Webpack отлично работает для разработки, но в производственных сборках не применяются какие-либо стили

#reactjs #webpack #sass

#reactjs #webpack #sass

Вопрос:

Моя конфигурация webpack в основном работает, но в производственных сборках не применяется стиль css.

Если я запускаю webpack с помощью этого скрипта узла webpack --mode development , а затем открываю ./dist/index.html файл в своем браузере, веб-сайт работает должным образом, и содержимое оформлено правильно.

Если я запускаю webpack с помощью этого скрипта узла webpack --mode production , а затем открываю ./dist/index.html файл в своем браузере, появляется содержимое веб-сайта, но к содержимому вообще не применяется стиль.

Сервер разработки webpack отлично работает со стилизованным контентом, текущими обновлениями и т. Д.

Я изменил свой webpack.config.js , чтобы закомментировать настройки, специфичные для производства, чтобы в производственных сборках и сборках для разработки использовалась одна и та же конфигурация webpack. Мне трудно объяснить, почему сборка разработки работает, но производственная сборка не работает даже с идентичной конфигурацией. Есть идеи о том, как устранить проблему?

Мой webpack.config.js выглядит так:

 const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env, { mode }) => {
  var config = {
    entry: {
      app: './src/app.js',
      react: 'react',
    },
    devtool: 'none',
    devServer: {
      contentBase: path.join(__dirname, 'dist'),
      compress: true,
      hot: true,
      port: 9000,
    },
    resolve: {
      extensions: ['.js', '.jsx', '.css', '.scss', '.sass'],
    },
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'index.[hash].js',
      chunkFilename: '[name].[contenthash].js',
    },
    optimization: {
      runtimeChunk: 'single',
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\/]node_modules[\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    },
    plugins: [
      new HtmlWebpackPlugin({
        minify: false,
        template: require('html-webpack-template'),
        inject: true,
        title: 'Octopedia',
        favicon: './public/images/favicon/favicon.ico',
        appMountId: 'app',
        meta: [{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }],
        links: [{ href: '/site.webmanifest', rel: 'manifest' }],
        appMountHtmlSnippet: '<div class="app-spinner"></div>',
        headHtmlSnippet:
          '<style>div.app-spinner{position:fixed;top:50%;left:50%;border:16px solid #f3f3f3;border-top:16px solid #3498db;border-radius:50%;width:120px;height:120px;margin:-60px 0 0 -60px;z-index:1;animation:spin 2s linear infinite;}@keyframes spin{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}</style >',
      }),
      new CopyWebpackPlugin({
        patterns: [{ from: 'public' }],
      }),
    ],
    module: {
      rules: [
        {
          test: /.(js|jsx)$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              cacheCompression: false,
              presets: ['@babel/preset-env', '@babel/preset-react'],
            },
          },
        },
      ],
    },
  };

  // if (mode === 'development') {
    config.mode = 'development';
    config.devtool = 'eval-source-map';
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    config.module.rules.push({
      test: /.(sass|scss|css)/,
      exclude: /node_modules/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
    });
  // } else {
  //   config.mode = 'production';
  //   config.devtool = 'none';
  //   config.plugins.push(new MiniCssExtractPlugin({ filename: 'index.css' }));
  //   config.module.rules.push({
  //     test: /.(sass|scss|css)/,
  //     exclude: /node_modules/,
  //     use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
  //   });
  // }

  return config;
};
  

Вывод из webpack в режиме разработки выглядит следующим образом:

                                     Asset       Size   Chunks                         Chunk Names
              app.6335bee170373d18f82e.js   39.8 KiB      app  [emitted] [immutable]  app
                              favicon.ico     15 KiB           [emitted]              
                         images/.DS_Store      6 KiB           [emitted]              
                 images/favicon/.DS_Store      6 KiB           [emitted]              
images/favicon/android-chrome-192x192.png    8.1 KiB           [emitted]              
images/favicon/android-chrome-512x512.png   23.3 KiB           [emitted]              
      images/favicon/apple-touch-icon.png   7.25 KiB           [emitted]              
         images/favicon/favicon-16x16.png  534 bytes           [emitted]              
         images/favicon/favicon-32x32.png   1.06 KiB           [emitted]              
               images/favicon/favicon.ico     15 KiB           [emitted]              
            index.87da9b99395c61fa6b02.js   33.4 KiB  runtime  [emitted] [immutable]  runtime
                               index.html   1.04 KiB           [emitted]              
            react.a0fc75f932f5dd9cdda1.js  133 bytes    react  [emitted] [immutable]  react
                         site.webmanifest  447 bytes           [emitted]              
          vendors.404f5d4ed29b35b04b91.js   2.41 MiB  vendors  [emitted] [immutable]  vendors
Entrypoint app = index.87da9b99395c61fa6b02.js vendors.404f5d4ed29b35b04b91.js app.6335bee170373d18f82e.js
Entrypoint react = index.87da9b99395c61fa6b02.js vendors.404f5d4ed29b35b04b91.js react.a0fc75f932f5dd9cdda1.js
  

Вывод из webpack в рабочем режиме выглядит следующим образом:

                                     Asset       Size  Chunks                                Chunk Names
              app.170943a8008bc467199f.js   2.77 KiB       2  [emitted] [immutable]         app
                              favicon.ico     15 KiB          [emitted]                     
                         images/.DS_Store      6 KiB          [emitted]                     
                 images/favicon/.DS_Store      6 KiB          [emitted]                     
images/favicon/android-chrome-192x192.png    8.1 KiB          [emitted]                     
images/favicon/android-chrome-512x512.png   23.3 KiB          [emitted]                     
      images/favicon/apple-touch-icon.png   7.25 KiB          [emitted]                     
         images/favicon/favicon-16x16.png  534 bytes          [emitted]                     
         images/favicon/favicon-32x32.png   1.06 KiB          [emitted]                     
               images/favicon/favicon.ico     15 KiB          [emitted]                     
            index.a162f7e38dd204127c0f.js   9.03 KiB       0  [emitted] [immutable]         runtime
                               index.html   1.04 KiB          [emitted]                     
            react.f87e0cf2654ac4f74576.js   71 bytes       3  [emitted] [immutable]         react
                         site.webmanifest  447 bytes          [emitted]                     
          vendors.dd9d4a26464d8d880c74.js    352 KiB       1  [emitted] [immutable]  [big]  vendors
  

Комментарии:

1. Я только что обнаружил, что могу сделать ‘webpack —config webpack.config.js —производство режима —профиль —json > компиляция-stats.json’. Сгенерированный файл compilation-stats.json не содержит ошибок и много ссылок на файлы scss. Ничего не выскакивает.

2. Из прочитанного, которое я сделал сегодня, я думаю, что это может быть связано с дрожанием дерева и побочными эффектами модуля. Поскольку ничто напрямую не ссылается ни на что внутри css, возможно, webpack удаляет его в качестве оптимизации.

Ответ №1:

Поскольку вы используете ту же конфигурацию, вам необходимо дополнительно добавить MiniCssExtractPlugin:

 
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const { NODE_ENV } = process.env;
const inDevelopment = NODE_ENV === "development";

module.exports = {
  ...options,
  module: {
    rules: [
        ...rules,
        {
          test: /.(scss|css)/,
          exclude: /node_modules/,
          use: [
                 inDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
                 'css-loader', 
                 'sass-loader'
               ],
        },
     ],
   },
   plugins: [
     ...plugins,
     !inDevelopment amp;amp; new MiniCssExtractPlugin({ filename: "bundle.min.css" }),
   ].filter(Boolean)
}
  

Короче говоря, если вы находитесь в разработке, он будет использоваться style-loader . Когда вы работаете, вы будете использовать MiniCssExtractPlugin.loader с MiniCssExtractPlugin плагином. Вышесказанное предполагает, что вы захотите объединить в один файл css. Если вы хотите разделить его на код, вам нужно добавить optimization.splitChunks и обновить плагин для разбиения на фрагменты.

Комментарии:

1. Я боролся с этой проблемой весь день и перепробовал много вещей, включая MiniCssExtractPlugin . В вашем примере определено имя файла для плагина, которое я раньше не пробовал, поэтому я попробовал еще раз, но это не имеет никакого значения. По-прежнему нет файла css, записанного в выходные данные, и нет ссылок css, вставленных в шаблон html.

2. Вот репозиторий , который вы можете использовать в качестве ссылки. Вы можете клонировать его и запускать локально, если хотите. Сосредоточьтесь на config папке и webpack.config.js файле. Я разделил конфигурацию, чтобы ее было легче читать, и включил массу заметок.

3. Спасибо, Мэтт. Я проверю это сегодня днем. Я также обновил свой вопрос своим последним кодом и результатами. В частности, если я создаю с --mode development помощью, содержимое ./dist папки будет работать так, как ожидалось, так что это не разница между сервером разработки, как я сначала подумал.

4. Dev-сервер должен использоваться только для разработки : "dev": "webpack-dev-server" . При сборке для производства вы должны использовать только webpack: "build": "webpack -p" ( -p сообщает webpack, что он создается для производства).

5. Спасибо, Мэтт, я это уже знаю и использую только dev server для разработки. Чтобы помочь сузить проблему, я попытался выполнить обычную сборку (не на сервере разработки), --mode development и теперь вывод в ./dist папке работает.

Ответ №2:

После нескольких дней ударов головой о кирпичную стену документации я, наконец, обнаружил проблему. Я считаю это ошибкой в webpack, но другие могут не согласиться.

Проблема возникает из-за того, что в «производственном» режиме webpack выполняет оптимизацию, которая удаляет код без ссылок. Это, конечно, хорошо, но оно не различает javascript и другие типы файлов, что для меня является ошибкой.

Конечным результатом является то, что, поскольку ни один из моих Javascript не ссылается на что-либо, экспортируемое из css (как это могло случиться, css не имеет механизма импорта / экспорта ES6), webpack tree shaker удаляет весь css из производственной сборки.

Решение очень простое, но его очень сложно найти. В вашем package.json файле вам нужно добавить:

   "sideEffects": ["*.css", "*.scss", "*.sass"],
  

Который сообщает webpack, что он не может удалить эти модули из дерева, потому что достаточно просто загрузить модуль, чтобы модуль обеспечивал функциональность без каких-либо ссылок в модуле, на которые ссылается приложение.

Потребовалось три дня экспериментов и чтения документов и статей stackoverflow, чтобы отследить это простое изменение в 1 строку. Я надеюсь, что этот ответ может избавить кого-то другого от траты того же времени.

Комментарии:

1. Пожалуйста, опубликуйте репозиторий Github, когда сможете, потому что я думаю, что вы все равно столкнетесь с проблемами при переносе его в производство.

2. Теперь ./dist папка содержит именно то, что я хочу. Какие проблемы вы ожидаете в производстве?

3. Если вы выводите, --mode development значит, вы используете неоптимизированные ресурсы в производстве. Если вы предотвращаете встряхивание дерева в процессе производства (используя sideEffects для CSS), то без пакета, передающего импорт CSS, он не сможет загрузить ваши компоненты / ресурсы. Обычной практикой является разделение импорта CSS из JS, чтобы их можно было импортировать отдельно (CSS в DOM head и JS в DOM body ). Если вы хотите объединить их в единое целое, я бы предложил CSS-in-JSS решение (например @emotion , styled-components , или styled-jsx )

4. Еще одна причина отделить CSS от JS — это если вы хотите использовать CDN или создать библиотеку пользовательского интерфейса NPM. Вместо того, чтобы заставлять разработчика использовать пакет, они могут просто импортировать скомпилированный JS и CSS отдельно. Таким образом, они не заблокированы webpack/gulp/rollup и не нуждаются в css-loader/style-loader/sass etc для обработки некомпилированных таблиц стилей.

5. Я не использую --mode development для производственных сборок, вы неправильно поняли мой комментарий. Я сказал, что я пробовал --mode development в качестве эксперимента, и именно результат этого эксперимента в конечном итоге привел меня к решению. Никто в здравом уме не будет развертывать сборку разработки для производства.