【问题标题】:Webpack works perfectly for development, but production builds do not have any styles appliedWebpack 非常适合开发,但生产构建没有应用任何样式
【发布时间】:2020-10-03 00:39:48
【问题描述】:

我的 webpack 配置大部分都可以工作,但生产版本没有应用 css 样式。

如果我使用此节点脚本 webpack --mode development 运行 webpack,然后在我的浏览器中打开 ./dist/index.html 文件,网站将按预期运行并且内容样式正确。

如果我使用此节点脚本 webpack --mode production 运行 webpack,然后在我的浏览器中打开 ./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

【问题讨论】:

  • 我刚刚发现我可以做 'webpack --config webpack.config.js --mode production --profile --json > compilation-stats.json'。生成的compilation-stats.json 没有错误,并且对scss 文件有很多引用。什么都没有跳出来。
  • 从我今天的阅读来看,我认为这可能与摇树和模块副作用有关。因为没有任何东西直接引用 css 中的任何东西,所以 webpack 可能会将其作为优化删除。

标签: reactjs webpack sass


【解决方案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 && new MiniCssExtractPlugin({ filename: "bundle.min.css" }),
   ].filter(Boolean)
}

简而言之,如果您正在开发中,它将使用style-loader。在生产环境中,您将使用 MiniCssExtractPlugin.loaderMiniCssExtractPlugin 插件。以上假设您希望捆绑到单个 css 文件中。如果要对其进行代码拆分,则需要添加optimization.splitChunks 并更新chunking 的插件。

【讨论】:

  • 我整天都在努力解决这个问题,并尝试了很多东西——包括 MiniCssExtractPlugin。您的示例为插件定义了一个文件名,我之前没有尝试过,所以我再次尝试,但没有任何区别。仍然没有写入输出的 css 文件,也没有在 html 模板中插入 css 链接。
  • 这是一个repo,您可以作为参考。如果您愿意,您可以克隆它并在本地运行它。关注config 文件夹和webpack.config.js 文件。我将配置进行了划分以使其更易于阅读并包含大量注释。
  • 谢谢马特。我今天下午去看看。我还用我最新的代码和发现更新了我的问题。具体来说,如果我使用--mode development 构建,那么./dist 文件夹的内容会按预期工作,所以这不是我最初认为的开发服务器差异。
  • Dev-server 只能用于开发:"dev": "webpack-dev-server"。在为生产构建时,您应该只使用 webpack:"build": "webpack -p"-p 告诉 webpack 它正在为生产构建)。
  • 谢谢马特,我已经知道了,并且只使用开发服务器进行开发。为了帮助缩小问题范围,我尝试使用--mode development 进行正常构建(不是开发服务器),./dist 文件夹中的输出现在可以工作了。
【解决方案2】:

在用头撞文件砖墙几天后,我终于找到了问题所在。我认为这是 webpack 中的一个错误,但其他人可能不同意。

问题的出现是因为在“生产”模式下,webpack 执行了删除未引用代码的优化。这当然是一件好事,但它不区分 javascript 和其他类型的文件,这对我来说是一个错误。

最终结果是,因为我的 Javascript 都没有引用从 css 导出的任何内容(怎么可能,css 没有 ES6 导入/导出机制),所以 webpack 摇树器丢弃了生产构建中的所有 css。

解决方案非常简单,但很难找到。在您的package.json 文件中,您需要添加:

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

这告诉 webpack 它不能从树中删除这些模块,因为仅仅加载模块就足以使模块提供功能,而无需应用程序引用模块中的任何内容。

我们花了三天的时间试验和阅读文档和 stackoverflow 文章来追踪这个简单的 1 行更改。我希望这个答案可以避免其他人花费相同的时间。

【讨论】:

  • 请尽可能发布 Github 存储库,因为我认为您在将其投入生产时仍然会遇到问题。
  • ./dist 文件夹现在包含我想要的内容。您预计生产中会遇到哪些问题?
  • 如果您在--mode development 中输出,那么您在生产中使用的是未优化的资产。如果您要防止生产中的树抖动(对 CSS 使用 sideEffects),那么如果没有捆绑器处理 CSS 导入,它将无法加载您的组件/资产。常见的做法是将 CSS 导入与 JS 分开,以便可以单独导入(DOM 的 head 中的 CSS 和 DOM 的 body 中的 JS)。如果您想将它们作为一个整体捆绑在一起,那么我建议您使用CSS-in-JSS 解决方案(例如@emotionstyled-componentsstyled-jsx
  • 将 CSS 与 JS 分开的另一个原因是,如果您想利用 CDN 或创建 NPM UI 库。与其强迫开发人员使用捆绑器,他们可以简单地分别导入编译后的 JS 和 CSS。这样,它们就不会被锁定在 webpack/gulp/rollup 中,也不需要 css-loader/style-loader/sass 等来处理未编译的样式表。
  • 我没有使用--mode development 进行生产构建,您误解了我的评论。我说的是我尝试--mode development作为一个实验,正是这个实验的结果最终引导我找到了解决方案。头脑正常的人不会将开发版本部署到生产环境中。
猜你喜欢
  • 2019-11-12
  • 1970-01-01
  • 1970-01-01
  • 2018-04-28
  • 2020-08-16
  • 2019-03-13
  • 1970-01-01
  • 1970-01-01
  • 2018-10-10
相关资源
最近更新 更多