| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | // Copyright 2020 The Hugo Authors. All rights reserved.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License");
 | 
					
						
							|  |  |  | // you may not use this file except in compliance with the License.
 | 
					
						
							|  |  |  | // You may obtain a copy of the License at
 | 
					
						
							|  |  |  | // http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software
 | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS,
 | 
					
						
							|  |  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					
						
							|  |  |  | // See the License for the specific language governing permissions and
 | 
					
						
							|  |  |  | // limitations under the License.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package js
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"fmt"
 | 
					
						
							|  |  |  | 	"io/ioutil"
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 	"os"
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:38:09 -05:00
										 |  |  | 	"path"
 | 
					
						
							| 
									
										
										
										
											2021-01-22 17:07:23 +01:00
										 |  |  | 	"path/filepath"
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:38:09 -05:00
										 |  |  | 	"regexp"
 | 
					
						
							| 
									
										
										
										
											2020-07-12 12:47:14 +02:00
										 |  |  | 	"strings"
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-21 10:09:33 +01:00
										 |  |  | 	"github.com/pkg/errors"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	"github.com/spf13/afero"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 	"github.com/gohugoio/hugo/hugofs"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	"github.com/gohugoio/hugo/common/herrors"
 | 
					
						
							| 
									
										
										
										
											2020-07-17 18:36:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | 	"github.com/gohugoio/hugo/hugolib/filesystems"
 | 
					
						
							| 
									
										
										
										
											2020-07-12 12:47:14 +02:00
										 |  |  | 	"github.com/gohugoio/hugo/media"
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | 	"github.com/gohugoio/hugo/resources/internal"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/evanw/esbuild/pkg/api"
 | 
					
						
							|  |  |  | 	"github.com/gohugoio/hugo/resources"
 | 
					
						
							|  |  |  | 	"github.com/gohugoio/hugo/resources/resource"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | // Client context for ESBuild.
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | type Client struct {
 | 
					
						
							|  |  |  | 	rs  *resources.Spec
 | 
					
						
							|  |  |  | 	sfs *filesystems.SourceFilesystem
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | // New creates a new client context.
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) *Client {
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	return &Client{
 | 
					
						
							|  |  |  | 		rs:  rs,
 | 
					
						
							|  |  |  | 		sfs: fs,
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type buildTransformation struct {
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:03:06 +02:00
										 |  |  | 	optsm map[string]interface{}
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	c     *Client
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *buildTransformation) Key() internal.ResourceTransformationKey {
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:03:06 +02:00
										 |  |  | 	return internal.NewResourceTransformationKey("jsbuild", t.optsm)
 | 
					
						
							| 
									
										
										
										
											2020-07-02 18:16:32 +02:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx) error {
 | 
					
						
							| 
									
										
										
										
											2020-07-12 12:47:14 +02:00
										 |  |  | 	ctx.OutMediaType = media.JavascriptType
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:03:06 +02:00
										 |  |  | 	opts, err := decodeOptions(t.optsm)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if opts.TargetPath != "" {
 | 
					
						
							|  |  |  | 		ctx.OutPath = opts.TargetPath
 | 
					
						
							| 
									
										
										
										
											2020-07-12 12:47:14 +02:00
										 |  |  | 	} else {
 | 
					
						
							|  |  |  | 		ctx.ReplaceOutPathExtension(".js")
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:59:03 +02:00
										 |  |  | 	src, err := ioutil.ReadAll(ctx.From)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	opts.sourcefile = ctx.SourcePath
 | 
					
						
							| 
									
										
										
										
											2020-11-04 19:09:40 +01:00
										 |  |  | 	opts.resolveDir = t.c.rs.WorkingDir
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:59:03 +02:00
										 |  |  | 	opts.contents = string(src)
 | 
					
						
							|  |  |  | 	opts.mediaType = ctx.InMediaType
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	buildOptions, err := toBuildOptions(opts)
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	buildOptions.Plugins, err = createBuildPlugins(t.c, opts)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:38:09 -05:00
										 |  |  | 	if buildOptions.Sourcemap == api.SourceMapExternal && buildOptions.Outdir == "" {
 | 
					
						
							|  |  |  | 		buildOptions.Outdir, err = ioutil.TempDir(os.TempDir(), "compileOutput")
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		defer os.Remove(buildOptions.Outdir)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-22 17:07:23 +01:00
										 |  |  | 	if opts.Inject != nil {
 | 
					
						
							|  |  |  | 		// Resolve the absolute filenames.
 | 
					
						
							|  |  |  | 		for i, ext := range opts.Inject {
 | 
					
						
							|  |  |  | 			impPath := filepath.FromSlash(ext)
 | 
					
						
							|  |  |  | 			if filepath.IsAbs(impPath) {
 | 
					
						
							|  |  |  | 				return errors.Errorf("inject: absolute paths not supported, must be relative to /assets")
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			m := resolveComponentInAssets(t.c.rs.Assets.Fs, impPath)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if m == nil {
 | 
					
						
							|  |  |  | 				return errors.Errorf("inject: file %q not found", ext)
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			opts.Inject[i] = m.Filename()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		buildOptions.Inject = opts.Inject
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	result := api.Build(buildOptions)
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 	if len(result.Errors) > 0 {
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		createErr := func(msg api.Message) error {
 | 
					
						
							|  |  |  | 			loc := msg.Location
 | 
					
						
							| 
									
										
										
										
											2021-01-21 10:09:33 +01:00
										 |  |  | 			if loc == nil {
 | 
					
						
							|  |  |  | 				return errors.New(msg.Text)
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 			path := loc.File
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var (
 | 
					
						
							|  |  |  | 				f   afero.File
 | 
					
						
							|  |  |  | 				err error
 | 
					
						
							|  |  |  | 			)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if strings.HasPrefix(path, nsImportHugo) {
 | 
					
						
							|  |  |  | 				path = strings.TrimPrefix(path, nsImportHugo+":")
 | 
					
						
							|  |  |  | 				f, err = hugofs.Os.Open(path)
 | 
					
						
							|  |  |  | 			} else {
 | 
					
						
							|  |  |  | 				var fi os.FileInfo
 | 
					
						
							|  |  |  | 				fi, err = t.c.sfs.Fs.Stat(path)
 | 
					
						
							|  |  |  | 				if err == nil {
 | 
					
						
							|  |  |  | 					m := fi.(hugofs.FileMetaInfo).Meta()
 | 
					
						
							|  |  |  | 					path = m.Filename()
 | 
					
						
							|  |  |  | 					f, err = m.Open()
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 			if err == nil {
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 				fe := herrors.NewFileError("js", 0, loc.Line, loc.Column, errors.New(msg.Text))
 | 
					
						
							|  |  |  | 				err, _ := herrors.WithFileContext(fe, path, f, herrors.SimpleLineMatcher)
 | 
					
						
							|  |  |  | 				f.Close()
 | 
					
						
							|  |  |  | 				return err
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			return fmt.Errorf("%s", msg.Text)
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 		var errors []error
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, msg := range result.Errors {
 | 
					
						
							|  |  |  | 			errors = append(errors, createErr(msg))
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 		// Return 1, log the rest.
 | 
					
						
							|  |  |  | 		for i, err := range errors {
 | 
					
						
							|  |  |  | 			if i > 0 {
 | 
					
						
							|  |  |  | 				t.c.rs.Logger.Errorf("js.Build failed: %s", err)
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-04 16:13:37 +01:00
										 |  |  | 		return errors[0]
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:38:09 -05:00
										 |  |  | 	if buildOptions.Sourcemap == api.SourceMapExternal {
 | 
					
						
							|  |  |  | 		content := string(result.OutputFiles[1].Contents)
 | 
					
						
							|  |  |  | 		symPath := path.Base(ctx.OutPath) + ".map"
 | 
					
						
							|  |  |  | 		re := regexp.MustCompile(`//# sourceMappingURL=.*\n?`)
 | 
					
						
							|  |  |  | 		content = re.ReplaceAllString(content, "//# sourceMappingURL="+symPath+"\n")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err = ctx.PublishSourceMap(string(result.OutputFiles[0].Contents)); err != nil {
 | 
					
						
							|  |  |  | 			return err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		_, err := ctx.To.Write([]byte(content))
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	} else {
 | 
					
						
							|  |  |  | 		_, err := ctx.To.Write(result.OutputFiles[0].Contents)
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:59:03 +02:00
										 |  |  | 	return nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-12 00:19:36 -04:00
										 |  |  | // Process process esbuild transform
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:59:03 +02:00
										 |  |  | func (c *Client) Process(res resources.ResourceTransformer, opts map[string]interface{}) (resource.Resource, error) {
 | 
					
						
							|  |  |  | 	return res.Transform(
 | 
					
						
							| 
									
										
										
										
											2020-10-05 13:34:14 +02:00
										 |  |  | 		&buildTransformation{c: c, optsm: opts},
 | 
					
						
							| 
									
										
										
										
											2020-07-21 17:59:03 +02:00
										 |  |  | 	)
 | 
					
						
							|  |  |  | }
 |