weixin_39707612
weixin_39707612
2021-01-10 04:40

css-loader + tree-shaking + transpiled imports (babel) not working as it should.

Do you want to request a feature or report a bug? Bug

What is the current behavior? When compiling a bundle with Webpack and css-loader in development mode, all css files in component-style subfolders are included. When doing a production build however, only the top level CSS is included (CSS directly within src folder), while all other css (in various subfolders of src) are missing from final bundle.

I tried removing all conditional statements based on isProduction from the webpack config. It still did not work. So, I assumed it had to do with tree shaking somehow.

Changing sideEffects in package.json did not work (it already had: ".css", ".scss").

By random chance I tried adding the following into babel-loader:

"plugins": ["/plugin-transform-modules-commonjs"]

Suddenly it worked.

What is going on here?

If the current behavior is a bug, please provide the steps to reproduce.


Webpack config:
const path = require('path');
const webpack = require('webpack'); // for webpack built-in plugins
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// const WriteFilePlugin = require('write-file-webpack-plugin');
// const ManifestPlugin = require('webpack-manifest-plugin');
// const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin');

// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const PATHS = {
  // when using __dirname, resolve and join gives same result,
  // because __dirname is absolute path to directory of this file.
  // OK to use no slashes,
  // both resolve and join adds platform-specific separators by default
  src: path.resolve(__dirname, 'src'),
  dist: path.resolve(__dirname, 'dist'),
  build: path.resolve(__dirname, 'build'),
  test: path.resolve(__dirname, 'test')
};

const NAMES = {
  // JS FILES
  main: 'main',
  print: 'print',
  // Chrome Extension Development
  popup: 'popup',
  options: 'options',
  background: 'background',
  contentScript: 'contentScript',

  // FOLDERS
  assets: 'assets',
  utilities: 'utilities',
  images: 'images',
  fonts: 'fonts',
  include: 'include'
};

const FILE_PATHS = {
  // JS
  mainJs: `${path.join(PATHS.src, NAMES.main)}.js`,
  printJs: `${path.join(PATHS.src, NAMES.print)}.js`,
  // Chrome Extension Development
  popupJs: `${path.join(PATHS.src, NAMES.popup)}.js`,
  optionsJs: `${path.join(PATHS.src, NAMES.options)}.js`,
  backgroundJs: `${path.join(PATHS.src, NAMES.background)}.js`,
  contentScriptJs: `${path.join(
    PATHS.src,
    NAMES.include,
    NAMES.contentScript
  )}.js`,

  // HTML
  mainHtml: `${path.join(PATHS.src, 'index')}.html`,
  printHtml: `${path.join(PATHS.src, NAMES.print)}.html`,
  // Chrome Extension Development
  popupHtml: `${path.join(PATHS.src, NAMES.popup)}.html`,
  optionsHtml: `${path.join(PATHS.src, NAMES.options)}.html`,
  backgroundHtml: `${path.join(PATHS.src, NAMES.background)}.html`
};

// Third-party (vendor) libraries to include
// const VENDORS = ['react', 'bootstrap', 'lodash', 'jQuery']; // Relative paths to node_modules

// Note: These are relative
const ASSETS = {
  images: path.join(NAMES.assets, NAMES.images),
  fonts: path.join(NAMES.assets, NAMES.fonts)
};

// CleanWebpackPlugin config
const pathsToClean = [PATHS.dist, PATHS.build];
const cleanOptions = {
  root: __dirname,
  exclude: ['shared.js'],
  verbose: true,
  dry: false
};

// CopyWebpackPlugin config
const copyPattern = [];
const copyOptions = {
  // ignore: ['*.js'],
  context: PATHS.src
};

module.exports = (env = {}) => {
  // webpack injects env variable, into webpack config.
  // perfect to check for production.
  // remember to specify --env.production in command
  // (if in production mode).
  const isProduction = env.production === true;

  return {
    entry: {
      main: FILE_PATHS.mainJs
      // vendor: VENDORS
    },
    mode: isProduction ? 'production' : 'development',
    devtool: isProduction ? 'source-map' : 'inline-source-map',
    optimization: {
      splitChunks: {
        chunks: 'all'
      }
    },
    output: {
      filename: isProduction ? '[name].[chunkhash:8].js' : '[name].js',
      // chunkFilename determine name of non-entry chunk files,
      // for example dynamic imports in the app
      chunkFilename: isProduction ? '[name].[chunkhash:8].js' : '[name].js',
      path: PATHS.dist
    },
    plugins: [
      // new webpack.SourceMapDevToolPlugin({
      // filename: '[file].map',
      // exclude: ['vendor', 'runtime']
      // }),
      new webpack.DefinePlugin({
        // specifies environment variable for dependencies.
        // does not apply to browser runtime environment
        // (process.env is provisioned by Node)
        'process.env.NODE_ENV': isProduction
          ? JSON.stringify('production')
          : JSON.stringify('development')
      }),
      // new BundleAnalyzerPlugin(),
      new CleanWebpackPlugin(pathsToClean, cleanOptions),
      new MiniCssExtractPlugin({
        // Options similar to the same options in webpackOptions.output
        // both options are optional
        // does not work with Hot Module Replacement (HMR)
        // allows HMR in development (will only use this plugin in production)
        filename: isProduction ? '[name].[contenthash].css' : '[name].css',
        chunkFilename: isProduction ? '[id].[contenthash].css' : '[id].css'
      }),
      new webpack.HashedModuleIdsPlugin(),
      isProduction
        ? new UglifyJSPlugin({
            cache: true,
            parallel: true,
            sourceMap: true // set to true if you want JS source maps
          })
        : () => {},
      new CopyWebpackPlugin(copyPattern, copyOptions),
      // new WriteFilePlugin(),
      new HtmlWebpackPlugin({
        template: FILE_PATHS.mainHtml,
        filename: `index.html`
      })
    ],
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /(node_modules|bower_components)/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['/preset-env'],
              "plugins": ["/plugin-transform-modules-commonjs"] // THIS MADE IT WORK
            }
          }
        },
        {
          test: /\.s?[ac]ss$/,
          exclude: /node_modules/,
          use: [
            isProduction
              ? MiniCssExtractPlugin.loader
              : {
                  // creates style nodes from JS strings
                  loader: 'style-loader',
                  options: {
                    sourceMap: true,
                    convertToAbsoluteUrls: true
                  }
                },
            {
              // CSS to CommonJS (resolves CSS imports into exported CSS string in JS bundle)
              loader: 'css-loader',
              options: {
                sourceMap: true,
                importLoaders: 3
                // url: false,
                // import: false
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                config: {
                  ctx: {
                    cssnext: {},
                    cssnano: {},
                    autoprefixer: {}
                  }
                },
                sourceMap: true
              }
            },
            {
              loader: 'resolve-url-loader',
              options: {
                attempts: 1,
                sourceMap: true
              }
            },
            {
              // compiles Sass to CSS
              loader: 'sass-loader',
              options: { sourceMap: true }
            }
          ]
        },
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: '[name].[hash:4].[ext]',
                outputPath: ASSETS.images
              }
            }
          ]
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: '[name].[hash:4].[ext]',
                outputPath: ASSETS.fonts
              }
            }
          ]
        },
        {
          test: /\.(csv|tsv)$/,
          use: ['csv-loader']
        },
        {
          test: /\.xml$/,
          use: ['xml-loader']
        },
        {
          test: /\.(html)$/,
          use: {
            loader: 'html-loader',
            options: {
              interpolate: 'require',
              minimize: true
            }
          }
        }
        // {
        // test: /\.tsx?$/,
        // exclude: /(node_modules|bower_components)/,
        // use: 'ts-loader'
        // }
      ]
    },
    devServer: {
      // contentBase: path.join(__dirname, 'dist'),
      contentBase: PATHS.dist,
      compress: false,
      port: 8080,
      open: false
    }
  };
};

What is the expected behavior? See above.

该提问来源于开源项目:webpack-contrib/css-loader

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

9条回答

  • weixin_39947016 weixin_39947016 4月前

    please create minimum reproducible test repo with minimum webpack config

    点赞 评论 复制链接分享
  • weixin_39707612 weixin_39707612 4月前

    I have now created a small test project. In the process, I discovered that the problem occurs when sideEffects: ["*.css"] is set in package.json. Any idea why? If I remove that part, all CSS gets included. This only applies to folders that are one level down from src (src css always gets included).

    Repo: https://github.com/magnusriga/css-loader

    点赞 评论 复制链接分享
  • weixin_39947016 weixin_39947016 4月前

    all css have sideEffects. What your expected (can you add minimum readme and provide what you have and what you expected)?

    点赞 评论 复制链接分享
  • weixin_39707612 weixin_39707612 4月前

    What I expected: When adding sideEffects: ["*.css"] to package.json, local css files should be protected against tree shaking (i.e. not be lost). That expected behavior is explained for instance here and here.

    What happened instead: When adding sideEffects: ["*.css"] to package.json, local css files were removed by tree shaking, instead of being protected from it.

    I have included a minimal working example above, to demonstrate the effect (https://github.com/magnusriga/css-loader). Just build once with sideEffects: ["*.css"] included in package.json and once with sideEffects: ["*.css"] not included, to see that all styles are only bundled in the latter build.

    点赞 评论 复制链接分享
  • weixin_39778447 weixin_39778447 4月前

    I don't think this is a problem with babel, but some difference between import and require. I've recently ran into this problem myself - not sure if its exactly the same as OP, but very similar. Here's a small repo: https://github.com/adamburgess/css-side-effects the meat is in test.js I wrote a test case on a matrix of different mode, sideEffects, import/require, and babel: https://travis-ci.org/adamburgess/css-side-effects/jobs/427420870#L450

    babel has no effect. when side effects is false and you use require, the css is not removed from the bundle (I think it should be) when side effects is ["*.css"] and you use import, css directly imported from the entry is in the bundle, but css imported from submodules is removed (this issue)

    点赞 评论 复制链接分享
  • weixin_39519798 weixin_39519798 4月前

    webpack treeshaking only works with ES6 module syntax, have you tried disabling the module transformation?

    Example:

    
    presets: ["/preset-env", { "modules": false }]
    
    点赞 评论 复制链接分享
  • weixin_39707612 weixin_39707612 4月前

    I have not tried that yet. I basically removed sideEffects in package.json and instead just imported all related css in each js component.

    点赞 评论 复制链接分享
  • weixin_39519798 weixin_39519798 4月前

    here is an example of Webpack tree shaking working with babel and css loader https://github.com/pp3nd3x/webpack-treeshaking-css-modules

    点赞 评论 复制链接分享
  • weixin_39947016 weixin_39947016 4月前

    Answer above with example, feel free to feedback

    点赞 评论 复制链接分享

相关推荐