我可以把它翻译成中文:我能否使用webpack分别生成CSS和JS文件?

120
我有以下内容:
  1. 想要捆绑的JS文件。
  2. 想要将其编译成CSS的LESS文件(将@imports解析为单个包)。
我希望将它们作为两个单独的输入进行指定,并且具有两个单独的输出(可能通过extract-text-webpack-plugin实现)。Webpack具有所有适当的插件/加载器来执行编译,但似乎不喜欢分离。
我看到有人从JS直接引用其LESS文件,例如require('./app.less');,没有其他原因,只是告诉webpack将这些文件包含在捆绑包中。这样可以只有一个入口点,但我认为这样做很错误-为什么我要在JS中引用LESS,当它与我的JS代码无关呢?
我尝试使用多个入口点,将入口JS和主要LESS文件一起处理,但是当使用多个入口点时,webpack会生成一个不会在加载时执行JS的打包文件-它将所有内容打包在一起,但不知道哪些应该在启动时执行。
我是不是使用webpack有误?对于这些单独的模块,我是否应该运行单独的webpack实例?如果我不打算将它们与我的JS混合,那么我是否应该使用webpack来处理非JS资产?

我可以推荐以下教程 https://medium.freecodecamp.org/how-to-combine-webpack-4-and-babel-7-to-create-a-fantastic-react-app-845797e036ff - wilo087
7个回答

33
如果我不需要将非JS资产与JS混合,那么是否应该使用webpack呢?
也许不需要。Webpack确实以js为中心,暗含你正在构建一个js应用程序。它的require()实现允许您将所有内容都视为模块(包括Sass / LESS局部文件、JSON等几乎任何内容),并自动为您处理依赖关系管理(将所有require的内容打包在一起,而不打包其他内容)。
为什么我要在JS中使用LESS呢?因为人们正在使用JS定义应用程序的一部分(例如React组件、Backbone View)。那部分应用程序有相应的CSS。依赖于某些外部CSS资源是单独构建且未直接从js模块引用,这种方式是脆弱的、难以处理,并可能导致样式过时等问题。Webpack鼓励您将所有内容都保持模块化,因此您可以有一个与JS组件配套的CSS(Sass等)局部文件,而JS组件则require()该文件以明确依赖关系(对您和构建工具来说,构建工具不会构建您不需要的样式)。
我不知道是否可以将CSS单独捆绑到webpack中(当CSS文件未从任何JS中引用时)。我确信您可以通过插件等连接一些东西,但不确定它是否可直接使用。如果您确实从JS中引用了CSS文件,则可以使用Extract Text插件将CSS轻松打包到单独的文件中,就像您说的那样。

27

使用 mini-css-extract 插件的 webpack 4 解决方案

Webpack 团队建议使用 mini-css-extract 替代 extract text 插件

此解决方案允许您创建一个仅包含 CSS 条目的单独代码块:

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

function recursiveIssuer(m) {
  if (m.issuer) {
    return recursiveIssuer(m.issuer);
  } else if (m.name) {
    return m.name;
  } else {
    return false;
  }
}

module.exports = {
  entry: {
    foo: path.resolve(__dirname, 'src/foo'),
    bar: path.resolve(__dirname, 'src/bar'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        fooStyles: {
          name: 'foo',
          test: (m, c, entry = 'foo') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
        barStyles: {
          name: 'bar',
          test: (m, c, entry = 'bar') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

这里是一个更为人为构造的例子,使用了我个人项目中的多个条目:

const ManifestPlugin = require('webpack-manifest-plugin')
const webpack = require('webpack')
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const VENDOR = path.join(__dirname, 'node_modules')
const LOCAL_JS = path.join(__dirname, 'app/assets/js')
const LOCAL_SCSS = path.join(__dirname, 'app/assets/scss')
const BUILD_DIR = path.join(__dirname, 'public/dist')
const EXTERNAL = path.join(__dirname, 'public/external')

function recursiveIssuer(m) {
  if (m.issuer) {
    return recursiveIssuer(m.issuer);
  } else if (m.name) {
    return m.name;
  } else {
    return false;
  }
}

module.exports = {
  entry: {
    vendor: [
      `${VENDOR}/jquery/dist/jquery.js`,
      `${VENDOR}/codemirror/lib/codemirror.js`,
      `${VENDOR}/codemirror/mode/javascript/javascript.js`,
      `${VENDOR}/codemirror/mode/yaml/yaml.js`,
      `${VENDOR}/zeroclipboard/dist/ZeroClipboard.js`,
    ],
    app: [
      `${LOCAL_JS}/utils.js`,
      `${LOCAL_JS}/editor.js`,
      `${LOCAL_JS}/clipboard.js`,
      `${LOCAL_JS}/fixtures.js`,
      `${LOCAL_JS}/ui.js`,
      `${LOCAL_JS}/data.js`,
      `${LOCAL_JS}/application.js`,
      `${LOCAL_JS}/google.js`
    ],
    'appStyles': [
      `${EXTERNAL}/montserrat.css`,
      `${EXTERNAL}/icons.css`,
      `${VENDOR}/purecss/pure-min.css`,
      `${VENDOR}/purecss/grids-core-min.css`,
      `${VENDOR}/purecss/grids-responsive-min.css`,
      `${VENDOR}/codemirror/lib/codemirror.css`,
      `${VENDOR}/codemirror/theme/monokai.css`,
    ]
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        appStyles: {
          name: 'appStyles',
          test: (m, c, entry = 'appStyles') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  module:  {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [ 'script-loader'],
      },
      {
        test: /\.(scss|css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
        ],
      },
    ],
  },
  mode: 'development',
  resolve: {
    extensions: ['.js', '.css', '.scss']
  },
  output: {
    path: BUILD_DIR,
    filename: "[name].[chunkhash].js",
  },
  plugins: [
    new ManifestPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    }),
  ]
};

我意识到这种方法不太模块化,但它可以为您提供基础,并且是在项目中采用webpack的优秀策略,特别是当您不希望混合javascript和css时。

这种方法的缺点是css-loader仍会生成一个额外的javascript文件(无论您选择使用与否), 在webpack 5中将会修复此问题

如果我不打算将非JS资源混入我的JS中,那么我甚至应该使用webpack吗?

我认为并没有什么问题,但最终取决于您管理多个构建系统的容忍度。对我而言,这感觉过于复杂,因此我更喜欢保持在webpack生态系统中。

有关上述策略的更多信息,请参见https://github.com/webpack-contrib/mini-css-extract-plugin#extracting-css-based-on-entry


今天这应该是默认答案。 - Giona Granata
1
现在在文档中:https://webpack.js.org/guides/entry-advanced/ - Ciro Santilli OurBigBook.com
虚拟的Js文件问题似乎在webpack 5中没有得到解决。此外,有人知道如何让Js和CSS输出文件具有相同的名称“index”,例如index.jsindex.css吗? 在此提问:https://dev59.com/DrD3oIgBc1ULPQZFD6pZ - Ciro Santilli OurBigBook.com
这个答案可能需要更新。来自mcep仓库的消息:它建立在webpack v5的新功能之上,需要webpack 5才能正常工作。 - aderchox

23

可以生成一个单独的CSS bundle,而不使用你的任何JS中的require('main/less),但正如Brendan在他回答的第一部分指出的那样,Webpack并不适用于全局CSS bundle与模块化JS并行的情况,但有几个选项可供选择。

第一种方法是为main.less添加一个额外的入口点,然后使用Extract Text插件创建CSS bundle:

var webpack = require('webpack'),
    ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        home: [
            'js/common',
            'js/homepage'
        ],
        style: [
            'styles/main.less'
        ]
    },
    output: {
        path: 'dist',
        filename: "[name].min.js"
    },
    resolve: {
        extensions: ["", ".js"]
    },
    module: {
        loaders: [{
            test: /\.less$/,
            loader: ExtractTextPlugin.extract("style", "css", "less")
        }]
    },
    plugins: [
        new ExtractTextPlugin("[name].min.css", {
            allChunks: true
        })
    ]
};

使用这种方法的问题是,除了生成捆绑包之外,还会生成一个不需要的JS文件,在这个例子中是style.js,它只是一个空的Webpack模块。

另一个选项是将主要的less文件添加到现有的Webpack入口点中:

var webpack = require('webpack'),
    ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        home: [
            'js/common',
            'js/homepage',
            'styles/main.less'
        ],
    },
    output: {
        path: 'dist',
        filename: "[name].min.js"
    },
    resolve: {
        extensions: ["", ".js"]
    },
    module: {
        loaders: [{
            test: /\.less$/,
            loader: ExtractTextPlugin.extract("style", "css", "less")
        }]
    },
    plugins: [
        new ExtractTextPlugin("[name].min.css", {
            allChunks: true
        })
    ]
};
如果你只有一个入口点的话,这是理想的选择,但如果你有多个入口点,那么你的Webpack配置会看起来有些奇怪,因为你不得不任意选择将主要less文件添加到哪个入口点中。

这样做可能会导致Webpack配置变得混乱,因为你需要手动指定哪个入口点需要包含主要的less文件。


1
ExtractTextPlugin自2017年10月起已停止使用(请参见https://www.npmjs.com/package/extract-text-webpack-plugin),其继任者是mini-css-extract插件(在此问题的单独答案中有所涵盖:https://dev59.com/2lsW5IYBdhLWcg3wKkcz#59237067)。 - observer

10

为了进一步澄清bdmason之前的答案 - 似乎理想的配置是为每个页面创建一个JS和CSS捆绑包,如下所示:

 entry: {
        Home: ["./path/to/home.js", "./path/to/home.less"],
        About: ["./path/to/about.js", "./path/to/about.less"]
    }

接着使用 [name] 开关:

output: {
        path: "path/to/generated/bundles",
        filename: "[name].js"
    },
plugins: new ExtractTextPlugin("[name].css")

完整配置 - 包括一些与问题无关的附加内容(实际上我们正在使用SASS而不是LESS):

var ExtractTextPlugin = require("extract-text-webpack-plugin");
var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
require('babel-polyfill');

module.exports = [{
    devtool: debug ? "inline-sourcemap" : null,
    entry: {
        Home: ['babel-polyfill', "./home.js","path/to/HomeRootStyle.scss"],
        SearchResults: ['babel-polyfill', "./searchResults.js","path/to/SearchResultsRootStyle.scss"]
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel-loader',
                query: {
                    presets: ['react', 'es2015'],
                    plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy']
                }
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract("style-loader","css-raw-loader!sass-loader")
            }
        ]
    },
    output: {
        path: "./res/generated",
        filename: "[name].js"
    },
    plugins: debug ? [new ExtractTextPlugin("[name].css")] : [
        new ExtractTextPlugin("[name].css"),
        new webpack.DefinePlugin({
            'process.env':{
                'NODE_ENV': JSON.stringify('production')
            }
        }),
        new webpack.optimize.UglifyJsPlugin({
            compress:{
                warnings: true
            }
        })
    ]
}
];

8

是的,这是可能的,但像其他人所说,您需要额外的软件包来实现(请参见package.json下的devDependencies)。以下是我用来编译我的bootstrap SCSS -> CSS和Bootstrap JS -> JS的示例代码。

webpack.config.js:

module.exports = {
    mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
    entry: ['./src/app.js', './src/scss/app.scss'],
    output: {
    path: path.resolve(__dirname, 'lib/modules/theme/public'),
    filename: 'js/bootstrap.js'
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: 'css/bootstrap.css',
                        }
                    },
                    {
                        loader: 'extract-loader'
                    },
                    {
                        loader: 'css-loader?-url'
                    },
                    {
                        loader: 'postcss-loader'
                    },
                    {
                        loader: 'sass-loader'
                    }
                ]
            }
        ]
    }
};

额外的 postcss.config.js 文件:

module.exports = {
    plugins: {
        'autoprefixer': {}
    }
}

package.json:

{
  "main": "app.js",
  "scripts": {
    "build": "webpack",
    "start": "node app.js"
  },
  "author": "P'unk Avenue",
  "license": "MIT",
  "dependencies": {
    "bootstrap": "^4.1.3",
  },
  "devDependencies": {
    "autoprefixer": "^9.3.1",
    "css-loader": "^1.0.1",
    "exports-loader": "^0.7.0",
    "extract-loader": "^3.1.0",
    "file-loader": "^2.0.0",
    "node-sass": "^4.10.0",
    "popper.js": "^1.14.6",
    "postcss-cli": "^6.0.1",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.26.1",
    "webpack-cli": "^3.1.2"
  }
}

请查看此处的教程:https://florianbrinkmann.com/en/4240/sass-webpack


5

像其他人提到的一样,您可以使用插件。

ExtractTextPlugin已被弃用。

您可以在webpack配置中使用当前推荐的MiniCssExtractPlugin

module.exports = {
     entry: {
        home: ['index.js', 'index.less']
     },
     plugins: [
            new MiniCssExtractPlugin({
                filename: '[name].css',
            }),
     ]
}

-1

你也可以将你的 Less require 语句放在入口 JS 文件中:

在 body.js 中

// CSS
require('css/_variable.scss')
require('css/_npm.scss')
require('css/_library.scss')
require('css/_lib.scss')

然后在webpack中

  entry: {
    body: [
      Path.join(__dirname, '/source/assets/javascripts/_body.js')
    ]
  },

const extractSass = new ExtractTextPlugin({
  filename: 'assets/stylesheets/all.bundle.css',
  disable: process.env.NODE_ENV === 'development',
  allChunks: true
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接