diff --git a/resources/resource_transformers/postcss/postcss.go b/resources/resource_transformers/postcss/postcss.go index 9015e120d1f..61123ba4347 100644 --- a/resources/resource_transformers/postcss/postcss.go +++ b/resources/resource_transformers/postcss/postcss.go @@ -400,6 +400,11 @@ func (imp *importResolver) shouldImport(s string) bool { return false } + // TODO1 + if strings.Contains(s, "tailwindcss") { + return false + } + return shouldImportRe.MatchString(s) } diff --git a/resources/resource_transformers/postcss/tailwindcss.go b/resources/resource_transformers/postcss/tailwindcss.go new file mode 100644 index 00000000000..82c869bde3e --- /dev/null +++ b/resources/resource_transformers/postcss/tailwindcss.go @@ -0,0 +1,141 @@ +// Copyright 2024 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 postcss + +import ( + "bytes" + "fmt" + "io" + + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/hexec" + "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/resources" + "github.com/gohugoio/hugo/resources/internal" + "github.com/gohugoio/hugo/resources/resource" +) + +// NewTailwindCSSClient creates a new TailwindCSSClient with the given specification. +func NewTailwindCSSClient(rs *resources.Spec) *TailwindCSSClient { + return &TailwindCSSClient{rs: rs} +} + +// Client is the client used to do TailwindCSS transformations. +type TailwindCSSClient struct { + rs *resources.Spec +} + +// Process transforms the given Resource with the PostCSS processor. +func (c *TailwindCSSClient) Process(res resources.ResourceTransformer, options map[string]any) (resource.Resource, error) { + return res.Transform(&tailwindcssTransformation{rs: c.rs, optionsm: options}) +} + +type tailwindcssTransformation struct { + optionsm map[string]any + rs *resources.Spec +} + +func (t *tailwindcssTransformation) Key() internal.ResourceTransformationKey { + return internal.NewResourceTransformationKey("tailwindcss", t.optionsm) +} + +type TailwindCSSOptions struct{} + +func debug(what ...any) { + fmt.Println(what...) // TODO1 +} + +func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformationCtx) error { + const binaryName = "tailwindcss" + + debug(binaryName) + + infol := t.rs.Logger.InfoCommand(binaryName) + infow := loggers.LevelLoggerToWriter(infol) + + ex := t.rs.ExecHelper + + // The tailwindcss CLI does not support streaming, so we need to write to temporary files. + // tempDir := os.TempDir() + // defer os.RemoveAll(tempDir) + + // outFilename := filepath.Join(tempDir, "hugo-tailwind-out.css") + workingDir := t.rs.Cfg.BaseConfig().WorkingDir + + var cmdArgs []any = []any{ + "--input=-", // Read from stdin. + //"--output", outFilename, + //"--cwd", workingDir, + "--optimize", // TDOO1 opts also: minimize? + } + + // TODO1 + // npm i tailwindcss @tailwindcss/cli + // npm i tailwindcss@next @tailwindcss/cli@next + // npx tailwindcss -h + + var errBuf bytes.Buffer + + stderr := io.MultiWriter(infow, &errBuf) + cmdArgs = append(cmdArgs, hexec.WithStderr(stderr)) + cmdArgs = append(cmdArgs, hexec.WithStdout(ctx.To)) + cmdArgs = append(cmdArgs, hexec.WithEnviron(hugo.GetExecEnviron(workingDir, t.rs.Cfg, t.rs.BaseFs.Assets.Fs))) + + cmd, err := ex.Npx(binaryName, cmdArgs...) + if err != nil { + if hexec.IsNotFound(err) { + // This may be on a CI server etc. Will fall back to pre-built assets. + return &herrors.FeatureNotAvailableError{Cause: err} + } + return err + } + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + + src := ctx.From + + imp := newImportResolver( + ctx.From, + ctx.InPath, + Options{}, // TODO1 + t.rs.Assets.Fs, t.rs.Logger, ctx.DependencyManager, + ) + + // TODO1 option { + src, err = imp.resolve() + if err != nil { + return err + } + + go func() { + defer stdin.Close() + io.Copy(stdin, src) + }() + + err = cmd.Run() + if err != nil { + if hexec.IsNotFound(err) { + return &herrors.FeatureNotAvailableError{ + Cause: err, + } + } + return imp.toFileError(errBuf.String()) + } + + return nil +} diff --git a/tpl/css/css.go b/tpl/css/css.go index 145cb3aadac..69027ac8c06 100644 --- a/tpl/css/css.go +++ b/tpl/css/css.go @@ -28,6 +28,7 @@ type Namespace struct { d *deps.Deps scssClientLibSass *scss.Client postcssClient *postcss.Client + tailwindcssClient *postcss.TailwindCSSClient babelClient *babel.Client // The Dart Client requires a os/exec process, so only @@ -63,7 +64,21 @@ func (ns *Namespace) PostCSS(args ...any) (resource.Resource, error) { return ns.postcssClient.Process(r, m) } -// Sass processes the given Resource with Sass. +// TailwindCSS processes the given Resource with tailwindcss. +func (ns *Namespace) TailwindCSS(args ...any) (resource.Resource, error) { + if len(args) > 2 { + return nil, errors.New("must not provide more arguments than resource object and options") + } + + r, m, err := resourcehelpers.ResolveArgs(args) + if err != nil { + return nil, err + } + + return ns.tailwindcssClient.Process(r, m) +} + +// Sass processes the given Resource with SASS. func (ns *Namespace) Sass(args ...any) (resource.Resource, error) { if len(args) > 2 { return nil, errors.New("must not provide more arguments than resource object and options") @@ -145,6 +160,7 @@ func init() { d: d, scssClientLibSass: scssClient, postcssClient: postcss.New(d.ResourceSpec), + tailwindcssClient: postcss.NewTailwindCSSClient(d.ResourceSpec), babelClient: babel.New(d.ResourceSpec), }