简体   繁体   中英

How can I split the JS and CSS into separate HTML files?

I have a very specific requirement where I need to split the JS script tag into one file and the CSS link tag into another file using HtmlWebpackPlugin .

At the moment, both script and link tags are going into both files. Is there a way to do them separately?

Here is my current Webpack file:

import webpack from 'webpack'
import path from 'path'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import autoprefixer from 'autoprefixer'

const extractCSS = new ExtractTextPlugin({
  filename: 'css/app.bundle.css',
  allChunks: true
})

const createCSSfile = new HtmlWebpackPlugin({
  chunks: ['app'],
  minify: {
    collapseWhitespace: true
  },
  hash: true,
  template: 'src/ejs/css.ejs',
  filename: 'templates/css.php'
})

const createJSfile = new HtmlWebpackPlugin({
  chunks: ['app'],
  minify: {
    collapseWhitespace: true
  },
  hash: true,
  template: 'src/ejs/js.ejs',
  filename: 'templates/js.php'
})

const config = {
  entry: {
    'app': [
      path.resolve(__dirname, 'src/js/app.js'),
      path.resolve(__dirname, 'src/scss/app.scss')
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/dist',
    filename: 'js/app.bundle.js',
    sourceMapFilename: 'js/app.bundle.map'
  },
  devtool: 'source-map',
  watch: true,
  watchOptions: {
    ignored: /node_modules/,
    aggregateTimeout: 300,
    poll: 1000
  },
  module: {
    rules: [
      {
        test: /\.(png|gif|jpg|jpeg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '/images/[name].[ext]'
            }
          }
        ]
      },
      {
        test: /\.(eot|ttf|woff|woff2|otf)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '/fonts/[name].[ext]'
            }
          }
        ]
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: [require('@babel/plugin-proposal-object-rest-spread')]
          }
        }
      },
      {
        test: /\.scss$/,
        use: extractCSS.extract([
          {
            loader: 'css-loader'
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins () {
                return [
                  autoprefixer({
                    browsers: [
                      'last 2 versions',
                      'Safari >= 8',
                      'Explorer >= 9',
                      'Android >= 4'
                    ]
                  })
                ]
              }
            }
          },
          {
            loader: 'sass-loader'
          }
        ])
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'js/app.common',
      filename: 'js/app.common.js',
      minChunks: 2
    }),
    createCSSfile,
    createJSfile,
    extractCSS
  ]
}

export default config

Each .ejs file is empty and generates the following inside the .php files:

<head><link href="/dist/css/app.bundle.css?bdba9ec6846a7d92d61f" rel="stylesheet"></head><script type="text/javascript" src="/dist/js/app.bundle.js?bdba9ec6846a7d92d61f"></script>

Is there a way to separate them?

Also, I noticed it is inserting a head tag for the CSS link; is there a way to stop this happening?

With help from @mootrichard I was able to get the answer I needed.

Steps to take:

  1. Separate JS and CSS into their own entry points.
  2. Set inject: false in HtmlWebpackPlugin configs to stop Webpack doing this.
  3. Reference 'common' in the chunks to make the common JS file available for the templates.
  4. Configure the .ejs templates to loop the files array.

webpack.config.babel.js

import webpack from 'webpack'
import path from 'path'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import autoprefixer from 'autoprefixer'

const extractCSS = new ExtractTextPlugin({
  filename: 'css/app.bundle.css',
  allChunks: true
})

const createCSSfile = new HtmlWebpackPlugin({
  chunks: ['css'],
  excludeChunks: ['js', 'common'],
  minify: {
    collapseWhitespace: true,
    preserveLineBreaks: true,
    removeComments: true
  },
  inject: false,
  hash: true,
  template: 'src/ejs/css.ejs',
  filename: 'templates/css.php'
})

const createJSfile = new HtmlWebpackPlugin({
  chunks: ['js', 'common'],
  excludeChunks: ['css'],
  minify: {
    collapseWhitespace: true,
    preserveLineBreaks: true,
    removeComments: true
  },
  inject: false,
  hash: true,
  template: 'src/ejs/js.ejs',
  filename: 'templates/js.php'
})

const config = {
  entry: {
    'css': [
      path.resolve(__dirname, 'src/scss/app.scss')
    ],
    'js': [
      path.resolve(__dirname, 'src/js/app.js')
    ]
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    publicPath: '/build',
    filename: 'js/app.bundle.js',
    sourceMapFilename: 'js/app.bundle.map'
  },
  devtool: 'source-map',
  watch: true,
  watchOptions: {
    ignored: /node_modules/,
    aggregateTimeout: 300,
    poll: 1000
  },
  module: {
    rules: [
      {
        test: /\.(png|gif|jpg|jpeg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '/images/[name].[ext]'
            }
          }
        ]
      },
      {
        test: /\.(eot|ttf|woff|woff2|otf)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '/fonts/[name].[ext]'
            }
          }
        ]
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: [require('@babel/plugin-proposal-object-rest-spread')]
          }
        }
      },
      {
        test: /\.scss$/,
        use: extractCSS.extract([
          {
            loader: 'css-loader'
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins () {
                return [
                  autoprefixer({
                    browsers: [
                      'last 2 versions',
                      'Safari >= 8',
                      'Explorer >= 9',
                      'Android >= 4'
                    ]
                  })
                ]
              }
            }
          },
          {
            loader: 'sass-loader'
          }
        ])
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      filename: 'js/app.common.js',
      minChunks: 2
    }),
    createCSSfile,
    createJSfile,
    extractCSS
  ]
}

export default config

js.ejs

<% for (let i = 0; i < htmlWebpackPlugin.files.js.length; i++) { %>
  <script src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
<% } %>

css.ejs

<% for (let i = 0; i < htmlWebpackPlugin.files.css.length; i++) { %>
  <link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[i] %>">
<% } %>

Hope this comes to help someone else in future.

Benefit of this approach

The reason I needed to separate out JS and CSS into actual separate files was for use in WordPress, where the templates do not have a concept of a 'master' template you inherit from, but instead has basic footer and header includes.

So if you're using WordPress then this is a pretty good approach to take.

Since you're wanting to have separate files with different contents, you probably want to split up your entry points and the filter your chunks.

In both of your instances of HtmlWebpackPlugin , you're setting chunks: ['app'] which includes both your CSS and your JS.

You could have something like:

entry: {
  'js': [
     path.resolve(__dirname, 'src/js/app.js')
  ],
  'css': [
     path.resolve(__dirname, 'src/scss/app.scss')
  ]
},

Then you could have:

const createCSSfile = new HtmlWebpackPlugin({
    chunks: ['css'],
    minify: {
        collapseWhitespace: true
    },
    hash: true,
    inject: false,
    template: 'src/ejs/css.ejs',
    filename: 'templates/css.php'
})

const createJSfile = new HtmlWebpackPlugin({
    chunks: ['js'],
    minify: {
        collapseWhitespace: true
    },
    hash: true,
    inject: false,
    template: 'src/ejs/js.ejs',
    filename: 'templates/js.php'
})

As for the CSS being included in the <head> , you'll want to set inject: false because you're utilizing your own custom templates for creating your HTML files. https://github.com/jantimon/html-webpack-plugin#configuration

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM