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