webpack5 的使用(三):加载 css

6,384 阅读3分钟

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

前言

webpack5 的使用(一):起步 文章里,介绍了如何管理 html 和 js,接触了 entry、output 和 plugins 等属性。这篇文章将补充如何管理 css、图片等资源以及 loader 的使用。

传统的加载资源方式,都是通过在 html 写上对应的标签进行加载资源,如:<style>。 webpack 并不是这样加载,它是以 js 模块为主导,通过不同的 loader 解析 js 中对应 import 的资源。

加载 css

加载方式1:style 标签

加载 css 需要安装 css-loader 和 style-loader。

npm i -D css-loader style-loader

css-loader 用来识别并加载 css,style-loader 用来将 css 转化为 html 的 style 节点。

webpack.config.js,我们需要新增一个属性 module,在里面配置 loader。

module.exports = {
    ...
    module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          'style-loader', 'css-loader'
        ]
      }
    ]
  }
}

我们先在 src 目录创建一个 css 目录,在 css 目录新建一个 index.css,在里面添加测试代码。

body {
  background: yellowgreen;
}

在 index.js 添加引入 css。

import '../css/index.css'

先用 npm run dev 测试一下,可以发现背景颜色已经更变了,打开控制台,也可以看见有 style 标签加入。

image.png

我们停止刚才的 dev 服务,再运行 npm run build,打开 dist/index.html。

image.png

好像并没有 <style> 标签,那 <style>标签从哪里来呢?
我们再打开 dist/main.js 看看。

image.png

可以发现这样一句样式代码,说明 <style> 是由 js 代码动态插入的。

加载方式2:独立 css 文件

一般来说,我们都希望 css 代码是独立在一个 css 文件里,与 html 和 js 分开来,而 mini-css-extract-plugin 插件就是用来将 css 提取到单独文件。

npm i -D mini-css-extract-plugin

既然我们决定要用 mini-css-extract-plugin,那么 style-loader 就不需要用到了。

webpack.config.js

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

module.exports = {
    ...
    plugins: [
        ...
        new MiniCssExtractPlugin()
    ],
    module: {
    rules: [
          {
            test: /\.css$/i,
            use: [
              //'style-loader', 'css-loader'
              MiniCssExtractPlugin.loader, 'css-loader'
            ]
          }
      ]
    }
}

运行 npm run build。
可以发现 dist 目录多了一个 main.css 文件,而且 index.html 里自动引入了 main.css 文件。

image.png

image.png

补充,有一个插件也是起到类似的功能,插件名叫 extract-text-webpack-plugin,不过在 webpack4+ 里,这个插件已经不被官方推荐了。

image.png

压缩 css 代码

细心的人可能会发现,打包后的 dist 目录下的 js 代码已经被压缩过了,但是 css 代码并没有压缩。现在我们可以利用 css-minimizer-webpack-plugin 来进行压缩 css 代码。

安装 css-minimizer-webpack-plugin

npm i -D css-minimizer-webpack-plugin

注意了,因为 development 环境不需要压缩代码,因此,我们只需要在 webpack.prod.js 写上压缩配置即可。

webpack.prod.js

const { merge } = require('webpack-merge')
const common = require('./webpack.config.js')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = merge(common, {
  mode: 'production',
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ],
  },
})

重新 build 一下,可以发现我们的 css 代码已经被压缩了

image.png

但是我们再打开 main.js 看看。
咦?为什么 js 没有压缩?

image.png

官方文档里的代码是这样写的,有这么一行注释

optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
},

我们试试按照官方,把注释代码取消注释。

optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      `...`,
      new CssMinimizerPlugin(),
    ],
},

再 build 一下,可以发现 js 代码也被压缩了。

image.png

在 webpack5 里 production 环境模式是会自动启用 terser-webpack-plugin,也即是压缩 js 代码,webpack5 是自带最新的 terser-webpack-plugin,因此不需要另外安装和启用。这里有点疑惑的是为什么 webpack5 不顺便把 css-minimizer-webpack-plugin 也自带一下,压缩 css 代码也很重要呀。

此外,也有一款不错压缩 js 的插件 uglifyjs-webpack-plugin,我没有深究 uglifyjs-webpack-plugin 和 terser-webpack-plugin 的区别,有欲望的小伙伴可以深究一下。

加载 scss、sass

安装 sass sass-loader

npm i -D sass sass-loader

我们在 webpack.config.js 添加多一个 rule,用来加载 scss、sass

module.exports = {
    module: {
        rules: [
          {
            test: /\.css$/i,
            use: [
              //'style-loader', 'css-loader'
              MiniCssExtractPlugin.loader, 'css-loader'
            ]
          },
          {
            test: /\.s[ac]ss$/i,
            use: [
              MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
            ]
          }
        ]
    }
}

在 src 创建多一个 scss 文件夹,再创建一个 index.scss 文件,里面写上测试代码。

body {
  background: burlywood;
}

index.js 引入 index.scss

import '../scss/index.scss'

build 一下。

image.png

发现 dist/main.css 引入了 scss 的代码,说明加载 scss成功。

整理文件

我们发现 dist 目录里的文件又是 html,又是 js,又是 css,杂乱无章,实际部署的项目肯定不能这样。

image.png

我们调整一下配置。

webpack.config.js

module.exports = {
    ...
    output: {
        filename: 'js/[name].[fullhash].js',
        ...
    },
    plugins: [
        ...
        new MiniCssExtractPlugin({
          filename: 'css/[name].[fullhash].css'
        })
    ],
    ...
}

生成的不同的文件就会放到不同的文件夹里。

image.png

补充

loader 是有顺序加载的,比如

{
    test: /\.s[ac]ss$/i,
    use: [
      MiniCssExtractPlugin.loader, 'css-loader',
      'sass-loader'
    ]
}

加载顺序:sass-loader > css-loader > MiniCssExtractPlugin.loader

规律是:从下往上,从右到左

完整代码

目录

image.png

index.js

import '../css/index.css'
import '../scss/index.scss'

console.log('这是一个入口文件')
console.log('环境变量:', process.env.NODE_ENV)

webpack.config.js

const path = require('path')

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

console.log('环境变量:', process.env.NODE_ENV)

module.exports = {
  // entry: path.resolve(__dirname, '../src/js/index.js'),
  entry: {
    main: path.resolve(__dirname, '../src/js/index.js'),
    header: path.resolve(__dirname, '../src/js/header.js'),
    footer: path.resolve(__dirname, '../src/js/footer.js'),
  },
  output: {
    // filename: 'main.js',
    filename: 'js/[name].[fullhash].js',
    path: path.resolve(__dirname, '../dist')
  },
  // devServer: {
  //   port: 3000,
  //   hot: true,
  //   contentBase: '../dist'
  // },
  plugins: [
    // new HtmlWebpackPlugin({
    //   title: '首页'
    // }),
    // 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/index.html'),
      filename: 'index.html',
      chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/header.html'),
      filename: 'header.html',
      chunks: ['header']
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/footer.html'),
      filename: 'footer.html',
      chunks: ['footer']
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[fullhash].css'
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          //'style-loader', 'css-loader'
          MiniCssExtractPlugin.loader, 'css-loader'
        ]
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
        ]
      }
    ]
  }
}

webpack.prod.js

const { merge } = require('webpack-merge')
const common = require('./webpack.config.js')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = merge(common, {
  mode: 'production',
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      `...`,
      new CssMinimizerPlugin(),
    ],
  },
})

系列文章

webpack5 的使用(零):概念
webpack5 的使用(一):起步
webpack5 的使用(二):多个环境配置
webpack5 的使用(三):加载 css
webpack5 的使用(四):加载资源文件
webpack5 的使用(五):babel 转译 js 代码
webpack5 的使用(六):优化