6.2.2 • Published 2 years ago

@mostlyserious/brightpack v6.2.2

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Dependencies

Install dependencies.

npm i @mostlyserious/brightpack tailwindcss @tailwindcss/typography --save-dev

or

yarn add @mostlyserious/brightpack tailwindcss @tailwindcss/typography --dev

Config

Run brightpack init to bootstrap all of the necessary config files.

  • .browserslistrc
  • .env
  • .eslintrc.js
  • babel.config.js
  • postcss.config.js
  • tailwind.config.js
  • webpack.config.js

package.json Script

Add the following scripts to your package.json

{
    "scripts": {
        "dev": "webpack --mode=development --watch",
        "build": "webpack --mode=production"
    }
}

External Config

API Keys for Our Custom Build Tool

Our current build system requires API keys to tie into existing services. To set these up, navigate to these two URLs and use your email to generate a new API keys.

Once you have these keys, you will create two hidden files in your home directory, .realfavicon and .tinypng. Paste each API key into its respective file with no other content. You're all set!

Required Back-end Utility (for Craft CMS)

BrightpackTwigExtensions.php

<?php

namespace Modules\TwigHelpers\TwigExtensions;

use Craft;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;

class BrightpackTwigExtensions extends AbstractExtension
{
    /**
     * @inheritdoc
     */
    public function getName()
    {
        return 'Brightpack';
    }

    /**
     * @inheritdoc
     */
    public function getFunctions()
    {
        return [
            new TwigFunction('entry', [$this, 'entry'], [
                'is_safe' => ['html']
            ]),
            new TwigFunction('asset', [$this, 'asset'], [
                'is_safe' => ['html']
            ]),
            new TwigFunction('external', [$this, 'external'], [
                'is_safe' => ['html']
            ])
        ];
    }

    /**
     * Returns versioned file(s) or the entire tag.
     *
     * @param  string     $file
     * @param  bool       $markup   (optional)
     * @param  bool       $manifest (optional)
     * @param  null|mixed $entry
     * @return string
     */
    public function entry($entry = null, $markup = true, $manifest = 'web/static/entries.json')
    {
        static $all;

        $results = [];
        $manifest_path = $this->join_path(CRAFT_BASE_PATH, $manifest);

        if (!is_file($manifest_path)) {
            return $markup ? '' : [];
        }

        $all = $all ?: json_decode(file_get_contents($manifest_path), true);

        if (!$entry) {
            return $all;
        }

        if (!isset($all[$entry])) {
            return [];
        }

        foreach ($all[$entry] as $i => $value) {
            $ext = pathinfo($value, PATHINFO_EXTENSION);

            switch ($ext) {
                case 'js' :
                    $result = $markup ? sprintf(
                        '<script src="%s" %s async defer></script>',
                        $value,
                        is_array($markup) ? $this->attr($markup, ['media']) : ''
                    ) : $value;
                    break;

                case 'css' :
                    $result = $markup ? sprintf(
                        '<link href="%s" rel="stylesheet" %s>',
                        $value,
                        is_array($markup) ? $this->attr($markup) : ''
                    ) : $value;
                    break;
                default :
                    $result = '';
                    break;
            }

            $this->preload($value);

            $results[] = $result;
        }

        return $markup ? implode(PHP_EOL, $results) : $results;
    }

    /**
     * Returns versioned file(s) or the entire tag.
     *
     * @param  string     $file
     * @param  bool       $markup   (optional)
     * @param  bool       $manifest (optional)
     * @param  null|mixed $entry
     * @return string
     */
    public function asset($entry = null, $markup = true, $manifest = 'web/static/assets.json')
    {
        static $all;

        $manifest_path = $this->join_path(CRAFT_BASE_PATH, $manifest);

        if (!is_file($manifest_path)) {
            return $markup ? '' : [];
        }

        $all = $all ?: json_decode(file_get_contents($manifest_path), true);

        if (!$entry) {
            return $all;
        }

        if (isset($all[$entry])) {
            return $all[$entry];
        }

        return null;
    }

    public function external($path)
    {
        if (is_readable(Craft::getAlias($path))) {
            return file_get_contents(Craft::getAlias($path));
        }

        return '';
    }

    protected function preload($resource)
    {
        static $pushed = [];

        if (!is_array($resource)) {
            $resource = [$resource];
        }

        foreach ($resource as $r) {
            $types = ['css' => 'style', 'js' => 'script'];
            $ext = pathinfo($r, PATHINFO_EXTENSION);
            $as = isset($types[$ext]) ? $types[$ext] : 'image';
            if (!in_array($r, $pushed)) {
                header(sprintf('Link: <%s>; as=%s; rel=preload;', $this->url($r), $as), false);
                $pushed[] = $r;
            }
        }

        return $resource;
    }

    protected function attr($attributes = [], $except = [])
    {
        $html = [];

        foreach ((array) $attributes as $key => $value) {
            if (!is_null($value)) {
                if (is_numeric($key)) {
                    if (!in_array($value, (array) $except)) {
                        $pair = $value;
                    }
                } else {
                    if (!in_array($key, (array) $except)) {
                        $pair = $key .'="'. $value .'"';
                    }
                }
            }

            if (!is_null($pair)) {
                $html[] = $pair;
            }
        }

        return count($html) > 0 ? ' '.implode(' ', $html) : '';
    }

    protected function join_path(...$paths)
    {
        return preg_replace_callback('/([^:])\/+/', function ($matches) {
            return $matches[1] . '/';
        }, implode('/', (array) $paths));
    }

    protected function url($path = '')
    {
        $host = parse_url($path, PHP_URL_HOST);

        return trim($host ? $path : $this->join_path(sprintf(
            '%s://%s',
            ($this->is_https() ? 'https' : 'http'),
            $this->request('SERVER_NAME')
        ), $path), '/');
    }

    protected function is_https()
    {
        if ($this->request('HTTPS')) {
            if ('on' === strtolower($this->request('HTTPS'))) {
                return true;
            }
            if ('1' === $this->request('HTTPS')) {
                return true;
            }
        } elseif ($this->request('SERVER_PORT') && ('443' === $this->request('SERVER_PORT'))) {
            return true;
        }

        return false;
    }

    protected function request($only = null, $object = true)
    {
        $return = null;

        $request = array_merge($_GET, $_POST, $_SERVER);

        if ($only) {
            if (!is_array($only)) {
                $return = isset($request[$only]) ? $request[$only] : null;
            } else {
                $return = [];

                foreach ($only as $key) {
                    $value = isset($request[$key]) ? $request[$key] : null;

                    if ($value) {
                        $return[$key] = $value;
                    }
                }
            }
        } else {
            $return = !empty($request) ? $request : null;
        }

        if (!empty($return)) {
            return ($object && is_array($return)) ? (object) $return : $return;
        }
    }
}

Twig Usage

entry() will load a defined Webpack entry. The default entry setup in webpack.config.js is 'app'

<head>
    {{ entry('app') }}
</head>

asset() will output the hashed URL to an optimized asset.

<img src="{{ asset('img/photo.jpg') }}">

Options

dest

Destination of compiled assets relative to webpack.config.js https://webpack.js.org/configuration/output/#outputpath

publicPath

Destination of compiled assets relative to the web root used for the full URL (leading slash will start at domain root, always include the trailing slash.) https://webpack.js.org/configuration/output/#outputpublicpath

watch

Files that aren't compiled that need to trigger a page reload when changed. (dev mode only) https://webpack.js.org/configuration/watch/#watch

filename

The name or template for the output files. Applies to all files—JS, CSS, images, etc. https://webpack.js.org/configuration/output/#outputfilename

chunkNameLength

Webpack generates chunks to shared code from being loaded more then it needs to be, the chunk names are generated by combining the filenames of an file that uses the chunk. This let's you truncate each filename used so that the combined length is not ridiculous. (default: 3)

name

If using multiple build configs, assign a unique name to keep them from clashing. See collegiatedayofprayer.org for an example of this.

port

If running more than one instance of the a config, whether in a single project or running dev for more than one project on your system at one time, you will need to provide a different port for webpack to use. (default: 8888)

mode

Automatically assigned dependent on the script run (either npm run dev or npm run build), but can be manually overwritten here.

What's Included

Loaders

Vue

vue-loader

Build caching is configured by default.

Babel

babel-loader

Babel will exclude node_modules by default. In this config I have made exceptions for svelte, vue, bootstrap out of the box.
If you have included another package that needs to be compiled from modern JS to support older browsers, add it as an exception in the webpack.config.js with the following code. In this example, xallarap has been added as an exception to the excluded directories.

brightpack.editLoader(config, 'babel-loader', (use, rule) => {
    rule.exclude = /node_modules\/(?!svelte|vue|bootstrap|xallarap)/;
});

Build caching is configured by default.

CSS

mini-css-extract-plugin (for build)
style-loader (for dev)
css-loader
postcss-loader

All of the loaders needed for CSS compilation have been included and configured out of the box. Nothing special going on, just basic requirements.

SASS

sass-loader

A little configuration to have SASS imports work the same as webpack imports.

LESS

less-loader

Images

tinify-loader
svgo-loader

Optimizes PNG, JPG, and SVG size with TinyPNG and SVGO.

Media, Fonts, Images

file-loader

No real compilation happens here, just copies files with the global file name configuration (which primarily applies to cache busting).

Raw

raw-loader

Allows you to include HTML and text files as strings in Javascript.
(I believe JSON is already supported by webpack by default.)

Svelte

svelte-loader

Compiles Svelte components, but also applies the same configuration as your other CSS, PostCSS, and SASS files to styles embedded in the components.

ESLint

eslint-loader

If you have eslint installed for you project, will add it to your webpack config for warning and errors to be displayed in the terminal.

Plugins

TerserPlugin

terser-webpack-plugin
Used to minify Javascript

ManifestPlugin

webpack-manifest-plugin
Creates two manifest files of all compiled files generated for a specific source file. This let's you use a function like entry('app') to get all generated Javascript and CSS files or asset('img/logo.png') to retrieve the full path to the cache busted filename.

CleanWebpackPlugin

clean-webpack-plugin
This removes all old files from previous builds so that only the current version of the files end up in the final assets directory.

MiniCssExtractPlugin

mini-css-extract-plugin
This extracts all of you CSS into actual CSS files since webpack simply wraps CSS in Javascript files and loads them dynamically by default. (which is not performance friendly)

CssoWebpackPlugin

csso-webpack-plugin
Used to minify CSS.

RemoveEmptyEntriesPlugin

(this is the only custom built loader or plugin in brightpack. There is no configuration, but can be removed like any other plugin.) Webpack sometimes generates "empty" javascript files for non-javascript assets it processes such as images or CSS, this cleans up (most of) these un-needed files.

MomentLocalesPlugin

moment-locales-webpack-plugin
Moment.js is extremely useful for working with dates and times in JS, but it also comes with a lot of localization files which result in a HUGE compiled file. If you're only developing for english, you don't need these extra localizations. This plugin removes them.

VueLoaderPlugin

vue-loader
This plugin is essentially just companion to the vue-loader.

Any plugin can be removed with the following code. You could then re-add the plugin with a different configuration if you needed.

brightpack.removePlugin(config, 'PluginName');