statix

package module
v0.0.0-...-99bd7c0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2018 License: MIT Imports: 11 Imported by: 0

README

Statix

unmaintained Build Status GoDoc Test Coverage Maintainability codebeat goreport


Statix is an asset manager written in go.

Assets are static files that are needed to run your application but are not part of your code. For example images and javascript files are assets. In web development, theses assets are stored in a public directory where they are accessible to the outside world through a web server.

The problem

You can work with your assets directly in your public directory, but this comes with some downsides :

1) assets are served as they are

The development version of an asset is also the version used in production. But the needs are different in these two environments.

For example, it is better if your javascript files are minified in production. You also want to reduce the number of javascript files downloaded by the browser to reduce loading time. On the contrary in your development environment you want to use uncompressed javascript and have your code split up in different files for readability.

If you want to combine the benefits of both versions, your working directory should not be the same as your public directory.

2) the URL of an asset does not change

The filename of an asset does not change when you update its content. After an update of your website, the client browser may still have the old version of an asset in cache. As its URL has not changed, the browser combines the old version of the asset (javascript, css, ...) with the new HTML. This may be catastrophic.

What can statix do ?

Statix can handle these two problems. It allows you to :

  • export assets from your working directory to a public directory where your static files are accessible through a web server
  • combine assets in one file (javascript files for example)
  • apply modifications to your assets (optimization, minification, compilation or anything you can imagine)
  • avoid browser cache problems (an asset filename will contain the md5 hash of its content and thus it will be unique)

Todo list

  • Add more unit tests
  • Add more alterations

Documentation

Configuration example

Your assets are defined in a Manager. Here is an example of configuration :

import (
    "github.com/sarulabs/statix"
    "github.com/sarulabs/statix/alteration"
    "github.com/sarulabs/statix/resource"
)

manager := statix.Manager{
    Server: statix.Server{
        // link a directory to a base URL
        Directory: "/output/directory",
        URL:       "http://example.com/static",
    },
    Filters: []statix.Filter{
        {
            // applies uglifyjs (a javascript minifier) to every .js asset
            Alteration: alteration.NewUglifyJs("/usr/local/bin/uglifyjs"),
            Pattern:    statix.NewExtensionPattern("js"),
        },
    },
    // asset definitions, the key of the map is the name of the asset.
    // an asset can be a SingleAsset (1 output file)
    // or an AssetPack (export all the files from a directory to another)
    Assets: map[string]statix.Asset{
        // exports .png files from /input/directory/img to /output/directory/img
        // and applies optipng to all files before writing them
        "images": statix.AssetPack{
            Input:   "/input/directory/img",
            Output:  "/output/directory/img",
            Pattern: statix.NewExtensionPattern("png"),
            Alteration: alteration.NewOptiPng("/usr/bin/optipng", 100),
        },
        // creates an app.js file that contains jquery
        // and a typescript file (app.ts) complied in javascript
        "app-js": statix.SingleAsset{
            Output: "/output/directory/app.js",
            Input: resource.NewCollection(
                resource.NewFile("/input/directory/js/jquery.js"),
                resource.NewAlteredResource(
                    resource.NewFile("/input/directory/app.ts"),
                    alteration.NewTypeScript("/usr/local/bin/tsc"),
                ),
            ),
        },
    },
}

You can learn more about each attribute of the Manager in the following parts of the documentation.

Dumping assets

To export your assets, you simply call the Dump method.

manager.Dump()

For each asset, two files are created. For example, an asset that you expect to be dumped in /output/directory/app.js will be composed of these two files :

  • /output/directory/app.{MD5}.js : the actual content of the asset ({MD5} being the md5 hash of the file).
  • /output/directory/app.js : a symlink to the other file.

The md5 hash is added to the filename to avoid cache problems in browsers. The symlink may seem useless but is in fact necessary to get the path of the asset without knowing the md5 hash of its content.

Getting URLs

You can get the URL of an asset thanks to the URL method. For a SingleAsset, you call URL with the name of the asset (the key of the map Manager.Assets).

manager.URL("app-js")

But for an AssetPack, the name of the asset is not enough. That is because an AssetPack may contain more than one asset. Thus you also need to specify the path of the file in the asset directory.

manager.URL("images", "header/logo.png")

With the manager given in example, this will return the URL of the file named /output/directory/img/header/logo.png.

For this to work, Manager.Server or Manager.Servers needs to be defined properly. Symlinks to asset files without md5 hash also need to exist.

Getting paths

You can also get the path of an asset. Symlink works the same way URL does but returns the filename of the symlink.

symlinkAppJs, _ := manager.Symlink("app-js") // for a SingleAsset
symlinkLogo, _ := manager.Symlink("images", "header/logo.png") // for an AssetPack

If you want to know the filename of the asset with the md5 hash, you will have to evaluate the symlink.

filename, _ := filepath.EvalSymlinks(symlink)

Manager.Server and Manager.Servers

Be aware that statix is not a web server. It only dumps assets in a directory where static files can be served through a web server.

However there is a link between the path of a static file and its URL. That is why if you set an URL for your asset directory, statix will be able to give you the URL of every asset in this directory.

In case you serve your assets in only one directory, the configuration will look like this :

statix.Manager{
    Server: statix.Server{
        Directory: "/output/directory",
        URL:       "http://example.com/static",
    },
    // ...
}

The URL of /output/directory/img/header/logo.{MD5}.png is http://example.com/static/img/header/logo.{MD5}.png.

You are not restricted to one server. You can define as many servers as you need :

statix.Manager{
    Servers: []statix.Server{
        {
            Directory: "/output/directory-1",
            URL:       "http://static1.example.com",
        },
        {
            Directory: "/output/directory-2",
            URL:       "http://static2.example.com",
        },
    },
    // ...
}

Manager.Assets

Assets are defined in a map. The key is the name of the asset. The value may be a SingleAsset or an AssetPack.

SingleAsset

A SingleAsset may be composed of a complex input but will be dumped in one file. It is useful if you need to :

  • combine assets (javascript files or stylesheets)
  • load an asset from a string and not from a file
Output

It is the name of the file where the content of the asset will be dumped.

Input

The input of a SingleAsset is a Resource. There are different kinds of Resource you may want to use as an input. Their definition is in the resource package. They all provide a Dump method to get their content.

A resource can be a string :

r := resource.NewString("my-resource")
c, err := r.Dump() // c is []byte("my-resource")

resource.NewBytes([]byte("my-resource")) // works too

But you probably have your resources stored in files :

r := resource.NewFile("/path/to/file")
c, err := r.Dump() // c is the content of the file

Sometimes you need to combined multiple resources to create a new one :

r := resource.NewCollection(
    resource.NewString("resource-1"),
    resource.NewString("resource-2"),
)
c, err := r.Dump() // c is []byte("resource-1resource-2")

You may also want to alter the content of a resource using an alteration :

r := resource.NewAlteredResource(
    resource.NewString("my-resource"),
    alteration.Reverse{}, // an alteration, Reverse does not exists but is used for comprehension
)
c, err := r.Dump() // c would be []byte("ecruoser-ym")

Alterations like alteration.Reverse{} are structures that implement the Alteration interface from the resource package. They are used to modify the content of an asset. They should have an Alter method that takes a resource and returns a new altered one.

You can find some alterations in the alteration package, but it is also really easy to create your own. In the alteration package you will find the alterations for theses programs :

  • jpegoptim
  • optipng
  • stylus
  • typescript
  • uglifycss
  • uglifyjs
AssetPack

An asset pack should be used if you want to export the content of a directory to an other. If you only have one asset, it is probably better to use a SingleAsset.

Output

This is the directory where you want to dump your assets.

Input

This is the directory where your raw assets are located.

Pattern

Pattern is just a wrapper on top of regular expressions. It will allow you to omit some files in the input directory. To only export files ending with .ext can for example use this pattern :

statix.NewPattern("\\\.ext$")

As filtering files from their extensions is a frequent use case, you may want to use the NewExtensionPattern function.

statix.NewExtensionPattern("png", "jpg") // will filter .png and .jpg files
Alterations

You can apply a list of alterations to all the assets in the AssetPack :

statix.AssetPack{
    Input:   "/input",
    Output:  "/output",
    Pattern: statix.NewExtensionPattern("png"),
    Alterations: []statix.Alteration{
        alteration.OptiPng("/usr/bin/optipng", 100),
    }
}

This will take all .png files in /input and apply optipng to their content before writing the results in the /output directory.

Manager.Filters

Filters allow you to apply some alterations to all your assets. A Filter is composed of an Alteration and a Pattern. The Pattern limits the effect of the Alteration to the files matching its regular expression.

In this example, uglifyjs is executed on every .js files :

statix.Manager{
    Filters: []statix.Filter{
        {
            Alteration: alteration.NewUglifyJs("/usr/local/bin/uglifyjs"),
            Pattern:    statix.NewExtensionPattern("js"),
        },
    },
    // ...
}

Filters are applied after alterations contained in SingleAsset and AssetPack. Minifying or optimizing files (javascript, css, images) depending on their extension is probably the main use case of Filters.

Manager.Input and Manager.Output

Manager.Input defines the root of all relative input paths. Identically Manager.Output defines the root of all relative output paths. These root paths apply for all assets and server directories. More precisely, if a server directory, an asset input or an asset output is relative, its path will be rewritten to be based in the Manager.Input or Manager.Output directory. But if its path is absolute nothing will happen and the asset or the server will stay the same.

This example is the same as the first one but with relative asset paths. Server directory is also relative because in this case an empty string equals ..

statix.Manager{
    Input:  "/input/directory",
    Output: "/output/directory",
    Server: statix.Server{
        // Directory: "/output/directory"
        Url: "http://example.com/static",
    },
    Assets: map[string]statix.Asset{
        "imgs": statix.AssetPack{
            Input:   "img", // /input/directory/img
            Output:  "img", // /output/directory/img
            Pattern: statix.NewExtensionPattern("jpg", "png"),
        },
        "app-js": statix.SingleAsset{
            Output: "app.js", // /output/directory/app.js
            Input: asset.NewCollection(
                asset.NewFile("js/jquery.js"), // /input/directory/js/jquery.js
                asset.NewFiltering(
                    asset.NewFile("app.ts"), // /input/directory/app.ts
                    filter.NewTypeScript("/usr/local/bin/tsc"),
                ),
            ),
        },
    },
}

Manager.Input and Manager.output are not mandatory. There are just here to simplify the Manager definition.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Asset

type Asset interface {
	RewritePaths(string, string) Asset
	Dump([]Filter) error
}

Asset is the interface for assets managed by statix Manager.

type AssetPack

type AssetPack struct {
	Input       string
	Output      string
	Pattern     Pattern
	Alterations []resource.Alteration
	Dumper      Dumper
}

AssetPack implements the Asset interface. It includes all the assets located in the AssetPack.Input directory. Only the files with an output (without md5 suffix) matching the AssetPack.Pattern are part of the AssetPack. When the asset is dumped with the AssetPack.Dumper, AssetPack.Filters are applied before writing the assets in the AssetPack.Output directory.

func (AssetPack) Dump

func (ap AssetPack) Dump(filters []Filter) error

Dump reads files contained in AssetPack.Input and dumps them in AssetPack.Output if they match AssetPack.Pattern. If some filters are defined in AssetPack.Filters, they will be applied before dumping the assets with the AssetPack.Dumper. If some filters are passed in the `filters` parameter, they will be applied just after filters in AssetPack.Filters.

func (AssetPack) InputFiles

func (ap AssetPack) InputFiles() ([]string, error)

InputFiles returns all the files contained in AssetPack.Input that are not directories and that match AssetPack.Pattern.

func (AssetPack) OutputFile

func (ap AssetPack) OutputFile(filename string, suffix string) (string, error)

OutputFile returns the absolute filename of an output based on the filename of the input. It also add a suffix in the basename (for example an md5 hash or a version number). The suffix is inserted just before the file extension and at the end of the filename if no extension was found.

func (AssetPack) RewritePaths

func (ap AssetPack) RewritePaths(input, output string) Asset

RewritePaths returns a new AssetPack with updated input and output. More precisely, if the AssetPack.Input or AssetPack.Output is relative, it is prefixed by the `input` and `output` parameters.

type Dumper

type Dumper interface {
	Dump(string, string, []byte) error
}

Dumper is the interface to dump asset content.

type FileDumper

type FileDumper struct{}

FileDumper implements the Dumper interface to dump assets into files.

func (FileDumper) Dump

func (fd FileDumper) Dump(filename, symlink string, data []byte) error

Dump write `data` in a file named `filename` and create a symlink named `symlink` to that file. If needed, directories will be created.

type Filter

type Filter struct {
	Alteration resource.Alteration
	Pattern    Pattern
}

Filter is the combination of an Alteration and a Pattern. The Pattern may allow to apply an Alteration only to some files.

type Manager

type Manager struct {
	Input   string
	Output  string
	Server  Server
	Servers []Server
	Filters []Filter
	Assets  map[string]Asset
}

Manager contains the definition of your asset and can dump them. It can also get the url of your assets.

  • Manager.Input will be the base directory for all your assets with a relative path. Assets with an absolute path will not be changed by this attribute.
  • Manager.Output works the same way as Manager.Input does but is the base directory in which your assets will be dumped.
  • Manager.Server defines an url for a directory. When your want to know the url of a given asset, this attribute will be used.
  • Manager.Servers is used like Manager.Server. Use it if your assets are in different directories.
  • Filters contains a list of filters that will be applied to all your assets before dumping them.
  • Assets contains all your assets. The key of the map is the name of the asset.
func (m Manager) AssetPackSymlink(ap AssetPack, filename string) (string, error)

AssetPackSymlink returns the filename of an asset symlink. It does not check if the symlink exists.

func (Manager) Dump

func (m Manager) Dump() error

Dump dumps all defined assets. If an asset path is relative, it is rewritten to be based in manager.Input and Manager.Output.

func (m Manager) SingleAssetSymlink(sa SingleAsset) (string, error)

SingleAssetSymlink returns the filename of an asset symlink. It does not check if the symlink exists.

func (m Manager) Symlink(assetName string, paths ...string) (string, error)

Symlink returns the filename of an asset symlink. For a SingleAsset, only the name of the asset is needed. For AssetPack, you also need to provide the path of the file inside the output directory

func (Manager) URL

func (m Manager) URL(assetName string, paths ...string) string

URL returns the url of an asset thanks to its name and what is defined in Manager.Server and Manager.Servers. If an error occurs, an empty string is returned.

If the asset is a SingleAsset, the name is enought to get its url. For example manager.Url("single") works.

But if the asset is an AssetPack, you also need to give its path inside the output directory. For example, manager.Url("pack", "/js/jquery.js") will look into the output directory of the asset named "pack" for the file {outputDirectory}/js/jquery.js

func (Manager) URLFromFilename

func (m Manager) URLFromFilename(filename string) (string, error)

URLFromFilename returns the url of an asset given its filename. It checks if the server defined in Manager.Server matches the filename. If not it checks all the servers defined in Manager.Servers one by one. If a correct server is found, the filename is rewritten into an url. If not, an empty string is returned.

func (m Manager) URLFromSymlink(symlink string) (string, error)

URLFromSymlink returns the url of an asset given its symlink.

type Pattern

type Pattern struct {
	Regexp string
}

Pattern is a wrapper for regular expressions.

func NewExtensionPattern

func NewExtensionPattern(extensions ...string) Pattern

NewExtensionPattern returns a Pattern with a regexp matching all the filenames with the extensions given in parameter.

func NewPattern

func NewPattern(regexp string) Pattern

NewPattern returns a Pattern based on a given `regexp`.

func (Pattern) Match

func (p Pattern) Match(s string) bool

Match tests if the `s` string matches the Pattern.

type Server

type Server struct {
	Directory string
	URL       string
}

Server maps a directory to an url.

type SingleAsset

type SingleAsset struct {
	Input  resource.Resource
	Output string
	Dumper Dumper
}

SingleAsset implements the Asset interface. It includes only one asset (SingleAsset.Input) implementing the Asset interface located in the asset package. The asset will be dump in the SingleAsset.Output file.

func (SingleAsset) Dump

func (sa SingleAsset) Dump(filters []Filter) error

Dump dumps the asset defined in SingleAsset.Input. If some filters are passed in the `filters` parameter, they will be applied before dumping the asset.

func (SingleAsset) OutputFile

func (sa SingleAsset) OutputFile(suffix string) (string, error)

OutputFile returns the absolute filename of the SingleAsset.Output. It also add a suffix in the basename (for example an md5 hash or a version number). The suffix is inserted just before the file extension and at the end of the filename if no extension was found.

func (SingleAsset) RewritePaths

func (sa SingleAsset) RewritePaths(input, output string) Asset

RewritePaths returns a new SingleAsset with updated input and output. More precisely, if the SingleAsset.Input or SingleAsset.Output path is relative, it is prefixed by the `input` and `output` parameters.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL