我可以使用 webpack 分别生成 CSS 和 JS 吗?

Posted

技术标签:

【中文标题】我可以使用 webpack 分别生成 CSS 和 JS 吗?【英文标题】:Can I use webpack to generate CSS and JS separately? 【发布时间】:2016-05-21 06:18:14 【问题描述】:

我有:

    我要捆绑的 JS 文件。 我想编译成 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 资产吗?

【问题讨论】:

我可以推荐以下教程medium.freecodecamp.org/… 【参考方案1】:

如果我不打算将它们混合到我的 JS 中,我什至应该将 webpack 用于非 JS 资产吗?

也许不是。 Webpack 绝对是以 js 为中心的,隐含的假设是你正在构建的是一个 js 应用程序。它对require() 的实现允许您将所有内容视为一个模块(包括 Sass/LESS 部分、JSON,几乎所有内容),并自动为您进行依赖管理(您 require 的所有内容都已捆绑,仅此而已) .

当我的 JS 与我的 JS 代码无关时,为什么我需要 LESS?

人们这样做是因为他们正在使用 js 定义应用程序的一部分(例如 React 组件、Backbone 视图)。该应用程序的那部分具有与之配套的 CSS。依赖于一些单独构建并且不直接从 js 模块引用的外部 CSS 资源是脆弱的,难以使用,并且可能导致样式过时等。Webpack 鼓励您保持一切模块化,因此您有一个 CSS (Sass,无论如何)与该 js 组件一起使用的部分,以及 js 组件require()s 它使依赖关系清晰(对您和构建工具而言,它从不构建您不需要的样式)。

我不知道你是否可以使用 webpack 自己捆绑 CSS(当 CSS 文件没有被任何 js 引用时)。我相信你可以用插件等连接一些东西,但不确定它是否可以开箱即用。如果你确实从你的 js 中引用了 CSS 文件,你可以很容易地将 CSS 与 Extract Text 插件捆绑到一个单独的文件中,正如你所说的。

【讨论】:

【参考方案2】:

可以在您的任何 JS 中不使用 require('main/less) 来生成单独的 CSS 包,但正如 Brendan 在他的回答的第一部分中指出的那样,Webpack 并不是为与模块化 JS 一起使用的全局 CSS 包而设计的,但是有几个选项。

首先是为 main.less 添加一个额外的入口点,然后使用 Extract Text 插件创建 CSS 包:

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 模块。

另一种选择是将 main 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
        )
    ]
;

如果您只有 1 个入口点,这是理想的选择,但如果您有更多入口点,那么您的 Webpack 配置会看起来有点奇怪,因为您必须任意选择将 main less 文件添加到哪个入口点。

【讨论】:

ExtractTextPlugin 自 2017 年 10 月起停产(参见 npmjs.com/package/extract-text-webpack-plugin),继任者是 mini-css-extract 插件(对此问题的单独答案涵盖:***.com/a/59237067/1746685)【参考方案3】:

为了进一步澄清 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
            
        )
    ]

];

【讨论】:

【参考方案4】:

您也可以将您的 Less 要求语句也放入您的条目 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
)

【讨论】:

【参考方案5】:

是的,这是可能的,但就像其他人说的那样,您需要额外的包才能这样做(请参阅 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

【讨论】:

【参考方案6】:

带有 mini-css-extract 插件的 webpack 4 解决方案

the webpack team recommends using mini-css-extract over the extract text plugin

此解决方案允许您创建一个单独的块,其中仅包含您的 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'
    ),
  ]
;

我意识到这种方法不是非常模块化,但它应该为您提供构建的基础,并且是在您不希望混合使用 javascript 和 css 的项目中采用 webpack 的绝佳策略。

这种方法的缺点是 css-loader 仍然会生成一个额外的 javascript 文件(无论您是否选择使用它),this will supposedly be fixed in webpack 5。

如果我不打算将它们混合到我的 JS 中,我什至应该将 webpack 用于非 JS 资产吗?

我认为这没有任何问题,但最终这取决于您对管理多个构建系统的容忍度。对我来说,这感觉有点矫枉过正,所以我更愿意留在 webpack 生态系统中。

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

【讨论】:

这应该是今天的默认答案 现在在文档中:webpack.js.org/guides/entry-advanced 虚拟 Js 文件问题 was not fixed in webpack 5 it seems。另外,有谁知道如何为 Js 和 CSS 输出文件使用相同的名称“索引”,例如index.jsindex.css?提问于:***.com/questions/70698775/…【参考方案7】:

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

ExtractTextPlugin 已弃用。

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

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

【讨论】:

以上是关于我可以使用 webpack 分别生成 CSS 和 JS 吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Webpack,是不是可以只生成 CSS,不包括 output.js?

是否可以让 html-webpack-plugin 从 css 生成 <style> 元素?

Webpack 为每次导入生成 css 文件

webpack打包怎么不生成map文件

Webpack + SASS + Autoprefixer 不生成 CSS 文件

webpack中使用html-webpack-plugin生成HTML文件并主动插入css和js引入标签