19 lut 2020

Usuwamy nadmiarowy Bootstrap + purgecss + webpack + pug template

Dzisiaj będzie krótko. Usuniemy nadmiarowe style z Bootstrap. Do tego zadania potrzebne nam będzie webpack, plugin purgecss-webpack-plugin, a za template posłuży nam pug oraz html.

Do testów użyłem dwóch przykładów ze strony bootstrapa, album oraz sign-in. Tak aby móc porównać czy działanie jest identyczne w wersji html i pug.

Najpierw tworzymy package.js

yarn init -y

Następnie dodamy wszystkie pluginy:

yarn add -D @babel/core @babel/preset-env autoprefixer babel-loader clean-webpack-plugin core-js cross-env css-loader cssnano html-webpack-plugin mini-css-extract-plugin node-sass postcss-cli postcss-loader pug pug-loader purgecss-webpack-plugin sass-loader style-loader terser-webpack-plugin webpack webpack-cli webpack-dev-server

W związku z tym że nasz przykład jest bardziej rozbudowany niże to co możemy zobaczyć w oficjalnej dokumentacji PurgeCSS to trochę tych pluginów jest 😉 Nie będę ich opisywał jedynie napiszę że są one między innymi do optymalizacji styli, konwersji e6 do es5, itd. Najważniejszy plugin to purgecss-webpack-plugin on odpowiada za usuwanie nadmiarowego css.

Użycie pluginu

Użycie jego jest bardzo proste, wystarczy tylko do webpacka dodać"

const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');

// ścieżka do naszych plików html oraz template
const PATHS = {
  src: path.join(__dirname, 'src')
}

A w sekcji z pluginami po prostu dodajemy:

plugins: [
  new PurgecssPlugin({
    paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true })
  })
  ...
]

Plugin ten sprawdza każdy plik w naszym przypadku w cały folderze src i szuka klas które są wykorzystywane.

Cała struktura src wygląda tak jak poniżej:

.
├── scss
│   ├── footer.scss
│   └── signin.scss
├── template-html
│   ├── form.html
│   └── index.html
├── template-pug
│   ├── form.pug
│   └── index.pug
├── bootstrap.min.css
├── form.js
└── index.js

Teraz należy w pliku package.json umieściłem sekcję za pomocą której można sterować jaka wersja będzie kompilowana. Tak jak na wstępie napisałem mamy dwie wersje html oraz pug.

"scripts": {
  "devhtml": "cross-env TYPE='html' webpack-dev-server --config webpack.config.js --mode development --open",
  "devpug": "cross-env TYPE='pug' webpack-dev-server --config webpack.config.js --mode development --open",
  "html": "cross-env TYPE='html' webpack --config webpack.config.js --mode production ",
  "pug": "cross-env TYPE='pug' webpack --config webpack.config.js --mode production"
}

A tak wygląda cały webpack

const path = require('path')
const glob = require('glob')
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const PurgecssPlugin = require('purgecss-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const type = process.env.TYPE;

const PATHS = {
  src: path.join(__dirname, 'src')
}

const ENTRY = {
  index: "./src/index.js",
  form: "./src/form.js"
}

const prodPlugin = (plugin, argv) => {
  return argv.mode === 'production' ? plugin : () => { };
}

const configureStyleLoader = mode => {
  return {
    test: /\.(css|sass|scss)$/,
    use: [
      mode === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader,
      {
        loader: 'css-loader',
        options: {
          importLoaders: 2,
          sourceMap: true,
        },
      },
      {
        loader: 'postcss-loader',
        options: {
          sourceMap: true,
        },
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: true,
        },
      }
    ],
  };
};

// Configure Clean Webpack
const configureCleanWebpack = () => {
  return {
    dry: false,
    verbose: false
  };
};

// Config Html
const entryHtmlPlugins = Object.keys(ENTRY).map(entryName => {
  return new HtmlWebpackPlugin({
    filename: `${entryName}.html`,
    template: `./src/template-${type}/${entryName}.${type}`,
    chunks: [entryName]
  });
});

// Configure Terser
const configureTerser = () => {
  return {
    cache: true,
    parallel: true,
    sourceMap: true,
  };
};

// Configure Pug Loader
const configurePugLoader = () => {
  return {
    test: /\.pug$/,
    loader: 'pug-loader',
    options: {
      pretty: true,
      self: true,
    },
  };
};

// Configure Optimization
const configureOptimization = () => {
  return {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor',
          chunks: 'all'
        },
        styles: {
          name: 'styles',
          test: /\.s?css$/,
          chunks: 'all',
          minChunks: 2,
          enforce: true,
        },
      },
    },
    minimizer: [new TerserPlugin(configureTerser())],
  };
};


module.exports = (env, argv) => {
  return {
    mode: argv.mode === "production" ? "production" : "development",
    entry: ENTRY,
    output: {
      filename: "vendor/js/[name].js",
      path: path.resolve(__dirname, `dist-${type}`),
      chunkFilename: 'vendor/js/[name].js'
    },
    optimization: configureOptimization(),
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: "babel-loader"
          }
        },
        configureStyleLoader(argv.mode),
        configurePugLoader()
      ]
    },
    plugins: [
      prodPlugin(new CleanWebpackPlugin(configureCleanWebpack()), argv),
      new MiniCssExtractPlugin({
        filename: "vendor/css/[name].css"
      }),
      new PurgecssPlugin({
        paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true })
      }),
    ].concat(entryHtmlPlugins)
  }
};

Podsumowanie

PurgeCSS to nie tylko purgecss-webpack-plugin ale także plugin do Gulp, Grunt, czy Gatsby. Zainteresowanych odsyłam do strony purgecss.com

Pod tym adresem znajduje się cały kod. Samemu można go przeanalizować.