Webpack + React. Как предварительно загрузить основной CSS файл? - PullRequest
3 голосов
/ 07 августа 2020

Я пытаюсь изучить React. js и Webpack. Итак, страница состоит из 3-х файлов: index.hmtl, style.css (создается MiniCssExtractPlugin ) и script.bundle.js. А Webpack добавляет стили следующим образом:

<head>
    <!-- other thigs -->
    <link href="/home/style.css" rel="stylesheet">
</head>

Но Google PageSpeed ​​Insights говорит, что лучше всего использовать предварительную загрузку для стилей.

Я попытался найти некоторую информацию о предварительной загрузке стилей. Я обнаружил, что есть PreloadCSSWebpackPlugin , но когда я добавляю его, webpack ничего не может построить:

Error: Plugin could not be registered at 'html-webpack-plugin-before-html-processing'. Hook was not found.

Итак, есть ли способ предварительно загрузить стили для веб-сайтов React ? Или на самом деле нам это не нужно?

webpack.config. js:

const autoprefixer = require('autoprefixer');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const os = require('os');
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const pages = require('./pages.json')
const PreloadCSSWebpackPlugin = require('preload-css-webpack-plugin');

module.exports = (env = {}) => {
  const isDev = env.MODE ? env.MODE === 'development' : true;

  const entries = {};
  const htmlPages = [];
  // eslint-disable-next-line no-restricted-syntax
  for (const [key, value] of Object.entries(pages)) {
    if (!value.innerHTML && !value.innerText) {
      entries[key] = `./${key}/script.jsx`;
    }
    htmlPages.push(
      new HtmlWebpackPlugin({
        filename: `${key === 'home' ? '' : `${key}/`}index.html`,
        template: './template.ejs',
        chunks: [key],
        description: value.description,
        keywords: value.keywords,
        title: value.title,
        robots: value.robots,
        isDev,
        minify: isDev ? false : {
          caseSensitive: false,
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: false,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          removeScriptTypeAttributes: true,
          keepClosingSlash: false,
          minifyJS: { compress: { conditionals: false } },
          minifyCSS: true,
          minifyURLs: true,
          sortAttributes: true,
          sortClassName: true,
        },
      }),
    );
  }

  return {
    mode: isDev ? 'development' : 'production',
    devtool: isDev ? 'source-map' : false,
    entry: entries,
    output: {
      path: env.hosting
        ? path.resolve(os.homedir(), 'public_html')
        : path.resolve(os.homedir(), 'Projects', 'MyInspire-ph.ru-React'),
      publicPath: '/',
      filename: `[name]/${isDev ? 'index.bundle' : '[hash]'}.js`,
      chunkFilename: `./assets/chunks/${isDev ? '[id]' : '[contenthash]'}.chunk.js`,
    },
    devServer: {
      port: 80,
      compress: true,
    },
    module: {
      rules: [
        {
          test: /\.jsx?$/i,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/react'],
              plugins: [
                '@babel/plugin-proposal-class-properties',
                '@babel/plugin-syntax-dynamic-import',
                '@babel/plugin-transform-runtime',
              ],
            },
          },
        },
        {
          test: /\.scss$/i,
          exclude: /node_modules/,
          use: [
            { loader: MiniCssExtractPlugin.loader },
            {
              loader: 'css-loader',
              options: {
                sourceMap: isDev,
                url: true,
              },
            },
            {
              loader: 'postcss-loader',
              options: {
                plugins: [autoprefixer()],
                sourceMap: isDev,
              },
            },
            {
              loader: 'sass-loader',
              options: { sourceMap: isDev },
            },
          ],
        },
        {
          test: /\.(otf|jpg|webp)$/,
          loader: 'file-loader',
          options: {
            outputPath: 'assets',
            name: '[name].[ext]',
          },
        },
      ],
    },
    optimization: {
      minimize: !isDev,
      minimizer: [
        new TerserPlugin({
          test: /.js$/i,
          extractComments: false,
          parallel: false,
          terserOptions: {
            output: {
              comments: false,
            },
          },
        }),
      ],
    },
    resolve: {
      extensions: ['.jsx', '.js'],
      alias: {
        '@elements': path.resolve(__dirname, 'assets', 'elements'),
        '@styles': path.resolve(__dirname, 'assets', 'styles'),
        '@assets': path.resolve(__dirname, 'assets'),
      },
    },
    plugins: [
      ...htmlPages,
      new MiniCssExtractPlugin({
        filename: `[name]/${isDev ? 'style' : '[hash]'}.css`,
      }),
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin({
        patterns: [
          { from: './favicon.ico', to: '.' },
          { from: './.htaccess', to: '.' },
          { from: './robots.txt', to: '.' },
          { from: './sitemap.xml', to: '.' },
          { from: './api', to: './api' },
          { from: './assets/photos', to: './assets/photos' },
          { from: './home/photos', to: './home/photos' },
          { from: './portfolio/photos', to: './portfolio/photos' },
          { from: './extra/locations/photos', to: './extra/locations/photos' },
          { from: './extra/poses/photos', to: './extra/poses/photos' },
          { from: './extra/studios/photos', to: './extra/studios/photos' },
        ],
      }),
      new PreloadCSSWebpackPlugin(),
    ],
  };
};

...