Posso usar o webpack para gerar CSS e JS separadamente?

86

Eu tenho:

  1. Arquivos JS que desejo agrupar.
  2. MENOS arquivos que desejo compilar em CSS (resolvendo @imports em um único pacote).

Eu esperava especificá-los como duas entradas separadas e ter duas saídas separadas (provavelmente por meio de extract-text-webpack-plugin). O Webpack tem todos os plugins / carregadores adequados para fazer a compilação, mas não parece gostar da separação.

Já vi exemplos de pessoas que exigem seus arquivos MENOS diretamente do JS, por exemplo require('./app.less');, por nenhum outro motivo a não ser dizer ao webpack para incluir esses arquivos no pacote. Isso permite que você tenha apenas um único ponto de entrada, mas parece muito errado para mim - por que eu exigiria menos em meu JS quando não tem nada a ver com meu código JS?

Tentei usar vários pontos de entrada, entregando o JS de entrada e o arquivo LESS principal, mas ao usar vários pontos de entrada, o webpack gera um pacote que não executa o JS no carregamento - ele agrupa tudo, mas não sabe o que deve ser executado na inicialização.

Estou apenas usando o webpack errado? Devo executar instâncias separadas de webpack para esses módulos separados? Devo usar webpack para ativos não JS se não for misturá-los ao meu JS?

Brent Traut
fonte
Posso recomendar o seguinte tutorial: medium.freecodecamp.org/…
wilo087

Respostas:

29

Devo usar webpack para ativos não JS se não for misturá-los ao meu JS?

Talvez não. Webpack é definitivamente centrado em js, com a suposição implícita de que o que você está construindo é um aplicativo js. Sua implementação de require()permite que você trate tudo como um módulo (incluindo parciais Sass / LESS, JSON, praticamente qualquer coisa) e automaticamente faz o gerenciamento de dependência para você (tudo o que você requireestá empacotado e nada mais).

por que eu exigiria menos em meu JS quando não tem nada a ver com meu código JS?

As pessoas fazem isso porque estão definindo uma parte de seu aplicativo (por exemplo, um componente React, uma Visualização de Backbone) com js. Essa parte do aplicativo tem CSS que a acompanha. Dependendo de algum recurso CSS externo que é construído separadamente e não referenciado diretamente do módulo js é frágil, mais difícil de trabalhar e pode levar a estilos que ficam desatualizados, etc. Webpack o incentiva a manter tudo modular, então você tem um CSS (Sass, qualquer que seja) parcial que acompanha aquele componente js, e o componente js require()é para tornar a dependência clara (para você e para a ferramenta de construção, que nunca cria estilos desnecessários).

Não sei se você poderia usar o webpack para agrupar CSS por conta própria (quando os arquivos CSS não são referenciados por nenhum js). Tenho certeza que você poderia conectar algo com plug-ins, etc., mas não tenho certeza se é possível fora da caixa. Se você fizer referência aos arquivos CSS de seu js, poderá facilmente agrupar o CSS em um arquivo separado com o plug-in Extrair Texto, como você disse.

Brendan Gannon
fonte
18

Um pacote CSS separado pode ser gerado sem usar require('main/less)em nenhum de seu JS, mas como Brendan apontou na primeira parte de sua resposta, o Webpack não foi projetado para que um pacote CSS global acompanhe o JS modular, no entanto, existem algumas opções .

A primeira é adicionar um ponto de entrada extra para main.less e, em seguida, usar o plug-in Extrair Texto para criar o pacote 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
        })
    ]
};

O problema com esse método é que você também gera um arquivo JS indesejado, bem como o pacote, neste exemplo: style.jsque é apenas um módulo Webpack vazio.

Outra opção é adicionar o arquivo less principal a um ponto de entrada Webpack existente:

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
        })
    ]
};

Isso é ideal se você tiver apenas 1 ponto de entrada, mas se tiver mais, sua configuração do Webpack parecerá um pouco estranha, pois você terá que escolher arbitrariamente em qual ponto de entrada adicionar o arquivo less principal.

maçom
fonte
10

Para esclarecer ainda mais a resposta anterior do bdmason - parece que a configuração desejável seria criar um pacote JS e CSS para cada página, assim:

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

Em seguida, use a [name]chave:

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

Configuração completa - com alguns acréscimos não relacionados à pergunta (na verdade, estamos usando SASS em vez de MENOS):

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
            }
        })
    ]
}
];
Gilad Barner
fonte
9

solução webpack 4 com plug-in mini-css-extract

a equipe do webpack recomenda o uso de mini-css-extract em vez do plug-in de extração de texto

esta solução permite que você crie um chunk separado contendo apenas suas entradas 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'],
      },
    ],
  },
};

Aqui está um exemplo mais elaborado usando entradas múltiplas de um dos meus projetos pessoais:

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'
    }),
  ]
};

Sei que essa abordagem não é muito modular, mas deve dar uma base para construir e é uma excelente estratégia para adotar webpack em projetos onde você não deseja misturar javascript e css.

A desvantagem dessa abordagem é que o css-loader ainda gera um arquivo javascript adicional (quer você escolha usá-lo ou não), isso supostamente será corrigido no webpack 5 .

Devo usar webpack para ativos não JS se não for misturá-los ao meu JS?

Não vejo nada de errado com isso, mas em última análise, depende da sua tolerância para gerenciar vários sistemas de compilação. Para mim, isso parece um exagero, então minha preferência é permanecer no ecossistema do webpack.

Para obter mais informações sobre as estratégias descritas acima, consulte https://github.com/webpack-contrib/mini-css-extract-plugin#extracting-css-based-on-entry

lfender6445
fonte
esta deve ser a resposta padrão hoje
Giona Granata
8

Sim, isso é possível, mas como outros disseram, você precisará de pacotes adicionais para fazer isso (consulte devDependencies em package.json). aqui está o código de exemplo que usei para compilar meu SCSS de bootstrap -> CSS e 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'
                    }
                ]
            }
        ]
    }
};

arquivo postcss.config.js adicional:

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"
  }
}

Veja o tutorial aqui: https://florianbrinkmann.com/en/4240/sass-webpack

geochanto
fonte
1

Como outros mencionados, você pode usar um plugin.

ExtractTextPlugin está obsoleto.

Você pode usar o atualmente recomendado MiniCssExtractPluginna configuração do seu webpack:

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

Você também pode colocar suas instruções Less require em seu arquivo JS de entrada:

em body.js

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

Então em 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
})
Ian Warner
fonte