簡體   English   中英

我可以使用 webpack 分別生成 CSS 和 JS 嗎?

[英]Can I use webpack to generate CSS and JS separately?

我有:

  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 資產嗎?

如果我不打算將它們混合到我的 JS 中,我什至應該將 webpack 用於非 JS 資產嗎?

也許不吧。 Webpack 絕對是以 js 為中心的,隱含假設您正在構建的是一個 js 應用程序。 它的require()實現允許您將所有內容視為模塊(包括 Sass/LESS 部分、JSON,幾乎所有內容),並自動為您進行依賴管理(您require內容都已捆綁,沒有其他內容)。

當它與我的 JS 代碼無關時,為什么我需要在我的 JS 中使用 LESS?

人們這樣做是因為他們正在用 js 定義他們的應用程序的一部分(例如一個 React 組件、一個主干視圖)。 應用程序的那部分具有與之配套的 CSS。 依賴於一些單獨構建而不直接從 js 模塊引用的外部 CSS 資源是脆弱的,更難使用,並且可能導致樣式過時等。Webpack 鼓勵你保持一切模塊化,所以你有一個 CSS (Sass,無論如何)部分與該 js 組件一起使用,並且 js 組件require()是為了明確依賴關系(對您和構建工具而言,它永遠不會構建您不需要的樣式)。

我不知道你是否可以使用 webpack 自己捆綁 CSS(當 CSS 文件沒有從任何 js 引用時)。 我確定你可以用插件等連接一些東西,但不確定它是否可以開箱即用。 如果您確實從 js 中引用了 CSS 文件,則可以使用提取文本插件輕松地將 CSS 捆綁到一個單獨的文件中,如您所說。

無需在任何 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 模塊。

另一種選擇是將主要的 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 配置將看起來有點奇怪,因為您必須任意選擇哪個入口點來添加 mainless 文件。

帶有 mini-css-extract 插件的 webpack 4 解決方案

webpack 團隊建議在提取文本插件上使用 mini-css-extract

此解決方案允許您創建一個單獨的塊,僅包含您的 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 文件(無論您是否選擇使用它), 這應該會在 webpack 5 中修復

如果我不打算將它們混合到我的 JS 中,我什至應該將 webpack 用於非 JS 資產嗎?

我認為這沒有任何問題,但最終取決於您對管理多個構建系統的容忍度。 對我來說這感覺有點矯枉過正,所以我更喜歡留在 webpack 生態系統中。

有關上述策略的更多信息,請參閱https://github.com/webpack-contrib/mini-css-extract-plugin#extracting-css-based-on-entry

為了進一步澄清 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
            }
        })
    ]
}
];

是的,這是可能的,但就像其他人所說的那樣,您需要額外的包才能這樣做(請參閱 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': {}
    }
}

包.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

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

不推薦使用ExtractTextPlugin

您可以在 webpack 配置中使用當前推薦的MiniCssExtractPlugin

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

你也可以把你的 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
})

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM