hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

build.go (5199B)

    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 	"errors"
   26 
   27 	"github.com/spf13/afero"
   28 
   29 	"github.com/gohugoio/hugo/hugofs"
   30 
   31 	"github.com/gohugoio/hugo/common/herrors"
   32 	"github.com/gohugoio/hugo/common/text"
   33 
   34 	"github.com/gohugoio/hugo/hugolib/filesystems"
   35 	"github.com/gohugoio/hugo/media"
   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.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 := ioutil.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.WorkingDir // where node_modules gets resolved
   87 	opts.contents = string(src)
   88 	opts.mediaType = ctx.InMediaType
   89 
   90 	buildOptions, err := toBuildOptions(opts)
   91 	if err != nil {
   92 		return err
   93 	}
   94 
   95 	buildOptions.Plugins, err = createBuildPlugins(t.c, opts)
   96 	if err != nil {
   97 		return err
   98 	}
   99 
  100 	if buildOptions.Sourcemap == api.SourceMapExternal && buildOptions.Outdir == "" {
  101 		buildOptions.Outdir, err = ioutil.TempDir(os.TempDir(), "compileOutput")
  102 		if err != nil {
  103 			return err
  104 		}
  105 		defer os.Remove(buildOptions.Outdir)
  106 	}
  107 
  108 	if opts.Inject != nil {
  109 		// Resolve the absolute filenames.
  110 		for i, ext := range opts.Inject {
  111 			impPath := filepath.FromSlash(ext)
  112 			if filepath.IsAbs(impPath) {
  113 				return fmt.Errorf("inject: absolute paths not supported, must be relative to /assets")
  114 			}
  115 
  116 			m := resolveComponentInAssets(t.c.rs.Assets.Fs, impPath)
  117 
  118 			if m == nil {
  119 				return fmt.Errorf("inject: file %q not found", ext)
  120 			}
  121 
  122 			opts.Inject[i] = m.Filename
  123 
  124 		}
  125 
  126 		buildOptions.Inject = opts.Inject
  127 
  128 	}
  129 
  130 	result := api.Build(buildOptions)
  131 
  132 	if len(result.Errors) > 0 {
  133 
  134 		createErr := func(msg api.Message) error {
  135 			loc := msg.Location
  136 			if loc == nil {
  137 				return errors.New(msg.Text)
  138 			}
  139 			path := loc.File
  140 			if path == stdinImporter {
  141 				path = ctx.SourcePath
  142 			}
  143 
  144 			errorMessage := msg.Text
  145 			errorMessage = strings.ReplaceAll(errorMessage, nsImportHugo+":", "")
  146 
  147 			var (
  148 				f   afero.File
  149 				err error
  150 			)
  151 
  152 			if strings.HasPrefix(path, nsImportHugo) {
  153 				path = strings.TrimPrefix(path, nsImportHugo+":")
  154 				f, err = hugofs.Os.Open(path)
  155 			} else {
  156 				var fi os.FileInfo
  157 				fi, err = t.c.sfs.Fs.Stat(path)
  158 				if err == nil {
  159 					m := fi.(hugofs.FileMetaInfo).Meta()
  160 					path = m.Filename
  161 					f, err = m.Open()
  162 				}
  163 
  164 			}
  165 
  166 			if err == nil {
  167 				fe := herrors.
  168 					NewFileErrorFromName(errors.New(errorMessage), path).
  169 					UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}).
  170 					UpdateContent(f, nil)
  171 
  172 				f.Close()
  173 				return fe
  174 			}
  175 
  176 			return fmt.Errorf("%s", errorMessage)
  177 		}
  178 
  179 		var errors []error
  180 
  181 		for _, msg := range result.Errors {
  182 			errors = append(errors, createErr(msg))
  183 		}
  184 
  185 		// Return 1, log the rest.
  186 		for i, err := range errors {
  187 			if i > 0 {
  188 				t.c.rs.Logger.Errorf("js.Build failed: %s", err)
  189 			}
  190 		}
  191 
  192 		return errors[0]
  193 	}
  194 
  195 	if buildOptions.Sourcemap == api.SourceMapExternal {
  196 		content := string(result.OutputFiles[1].Contents)
  197 		symPath := path.Base(ctx.OutPath) + ".map"
  198 		re := regexp.MustCompile(`//# sourceMappingURL=.*\n?`)
  199 		content = re.ReplaceAllString(content, "//# sourceMappingURL="+symPath+"\n")
  200 
  201 		if err = ctx.PublishSourceMap(string(result.OutputFiles[0].Contents)); err != nil {
  202 			return err
  203 		}
  204 		_, err := ctx.To.Write([]byte(content))
  205 		if err != nil {
  206 			return err
  207 		}
  208 	} else {
  209 		_, err := ctx.To.Write(result.OutputFiles[0].Contents)
  210 		if err != nil {
  211 			return err
  212 		}
  213 	}
  214 	return nil
  215 }
  216 
  217 // Process process esbuild transform
  218 func (c *Client) Process(res resources.ResourceTransformer, opts map[string]any) (resource.Resource, error) {
  219 	return res.Transform(
  220 		&buildTransformation{c: c, optsm: opts},
  221 	)
  222 }