webpack 打包前端静态项目

前言

最近新写了一个 H5 项目,不需要后端交互,使用了原生js,然后再次体会到webpack的强大。

最开始项目只有一个index.html和 几个js文件,js文件直接在index.html通过<script>标签进行依次引入,感觉没有太大问题。之后这个项目在服务器使用nginx做个简单托管也就部署好了,但是部署的时候我需要对这些js文件进行压缩来缩小资源体积,同时转换ES6、ES7语法到浏览器普遍支持的 ES5,我当时就想反正就几个js文件嘛,按照在index.html的引入顺序全部复制到一个js文件里,再把这个文件复制到一个js在线压缩工具进行压缩就OK啦。

<script src="preset.js"></script>
<script src="utils.js"></script>
<script src="features.js"></script>
<script src="main.js"></script>
<!-- 最初傻瓜想法:按引入顺序把其内容复制到一个新js文件里,再压缩好了复制回这里只留一个 script 标签 -->

之后为了进一步减小项目体积,我又把htmlcss也复制到对应的在线压缩工具进行压缩,最后又为了减少网络请求,又将css内容直接放到headstyle下,而不使用link标签。

后来js逻辑变多了,得使用模块语法了,Oops!这可怎么办呢,然后我就想到了webpack~。我最后只需要yarn build一下,一切问题都搞定了。

配置 webpack

可参考webpack 中文文档

  1. 使用yarn添加一下webpack,顺便生成package.json

    yarn add -D webpack webpack-cli
    
  2. 写一个简单的配置文件webpack.config.js设置一下入口文件和输出文件。

    const path = require('path')
    
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
      },
    }
    

    项目src/index.js这个入口文件就使用了模块语法,举个例:

    import AudioController from './AudioController'
    import { refreshAll } from './Timer'
    
    const canvas = document.getElementById('content')
    const ctx = canvas.getContext('2d')
    
    canvas.onclick = event => {
      const relativeY = event.pageY - canvas.offsetTop
      if (relativeY > 200 && relativeY < 400) {
        refreshAll()
        ctx.fillText('Welcome to Anand\'s Blog', 120, 120)
      }
    }
    
  3. 为 webpack 打包写一个script,在package.jsonscripts添加build操作,这样之后打包只需要运行yarn build就好啦。

    {
      "private": true,
      "scripts": {
        "build": "webpack"
      },
      "devDependencies": {
        "webpack": "^5.13.0",
        "webpack-cli": "^4.3.1"
      }
    }
    
  4. 使用yarn build命令进行打包,在dist目录会生成bundle.js文件,然后去除index.html里之前引入js文件的script标签,现在不需要再像之前一样还要考虑引入顺序,只需要引入这个bundle.js就好啦。

    <script src="dist/bundle.js"></script>
    

Nice! 这样简单的配置,就解决了之前复制几个js文件到一起然后再压缩、转语法的操作了。

丰富 webpack

虽然现在已经能满足js文件打包、压缩等需求了,但是js文件里使用图片、音频等文件等路径怎么办呢?

图片、音频资源

如何项目只有图片资源,只需要简单添加module规则配置。

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'
      }
    ]
  }
}

但是我的这个项目还会用到音频资源,所以就需要借助file-loader了,先安装一下。

yarn add -D file-loader

顺便配置一下资源解析,每次引入图片、音频这些资源的路径都需要./../../慢慢去找,这也太麻烦了吧。我们配置一下resolve

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 只加了 mp3 格式,其他的也可以,继续加就好啦
        test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
        loader: 'file-loader',
        options: {
          // [path]、[hash] 这些都是占位符,其他的可以看 file-loader 文档
          name: '[path][hash:18].[ext]'
        }
      }
    ]
  },
  resolve: {
    alias: {
      // assets/ 是我项目资源的位置
      // 之后使用 import titleImage from 'Assets/images/title.png' 就可以啦,require 也一样
      Assets: path.resolve(__dirname, 'assets/')
    }
  }
}

但是项目中new Audio('Assets/audios/demo.mp3')会存在问题,在chrome的开发工具看请求是[object%20Module],挺奇怪。

object20module

之后发现这些资源的解析不需要使用到ES modules语法,我们把它关掉:

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
        loader: 'file-loader',
        options: {
          name: '[path][hash:18].[ext]',
          // 还有一个解决方案是使用 require('Assets/audios/demo.mp3').default 而不用关闭这个配置
          esModule: false
        }
      }
    ]
  },
  resolve: {
    alias: {
      Assets: path.resolve(__dirname, 'assets/')
    }
  }
}

配置需要的插件

现在还存在其他需求:html文件的压缩,js文件自动引入到htmlcss文件的压缩等。

html-webpack-plugin

如果修改了打包输出文件名,或者文件名使用了hash,这样打包都都需要手动去修改index.htmljs文件的引入,太麻烦了,我们使用html-webpack-plugin插件来解决,同时解决了压缩需求。

  1. 安装一下

    yarn add -D html-webpack-plugin clean-webpack-plugin
    

    clean-webpack-plugin插件可以每次打包前自动删除打包生成的dist文件夹,可以不用。

  2. 配置一下

    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      // 为了避免代码块太长,这里省略啦其他配置项
      plugins: [
        new CleanWebpackPlugin(),
        // 使用项目的 index.html 作为模板,打包后会在 dist 生成一个新的 index.html(插入了 bundle.js 的 <script> 引入标签)
        new HtmlWebpackPlugin({
          template: 'index.html'
        })
      ]
    }
    

mini-css-extract-plugin

使用mini-css-extract-plugin插件整合项目使用的多个css文件到一个文件。简单使用可以看它的文档,这里我的需求是项目不存在import 'Assets/styles/main.css'这样的资源使用,只在index.html中通过link加载。所以我们需要在 webpack 的entry入口配置中添加css文件,然后再利用这个插件,并且html-webpack-plugin插件也自动把输出文件引入到了index.html,完美。

  1. 安装一下,同时需要用到css-loader

    yarn add -D mini-css-extract-plugin css-loader
    
  2. webpack 配置中添加这个插件

    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    
    module.exports = {
      // 为了避免代码块太长,这里省略啦其他配置项
      entry: [
        './src/index.js',
        './assets/styles/main.css'
      ],
      plugins: [
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin(),
        new HtmlWebpackPlugin({
          template: 'index.html'
        })
      ],
      module: {
        rules: [
          // ... 其他已省略
          {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader, 'css-loader']
          }
        ]
      }
    }
    

css-minimizer-webpack-plugin

你或许发现了mini-css-extract-plugin插件并没有压缩输出的css文件。之前这个需求我们一般用的optimize-css-assets-webpack-plugin,但是 webpack 5 及更高版本建议使用了css-minimizer-webpack-plugin

  1. 安装一下

    yarn add -D css-minimizer-webpack-plugin
    
  2. 配置一下

    const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
    
    module.exports = {
      // 为了避免代码块太长,这里省略啦其他配置项
      optimization: {
        minimize: true,
        minimizer: [
          new CssMinimizerPlugin(),
        ]
      },
    }
    

但是使用它之后出现了一下新问题,那就是之前打包的生成的bundle.js文件不是之前压缩的样子了,这里设置了minimizer后需要加上 webpack 5 本身带有的terser-webpack-plugin优化。

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserWebpackPlugin = require("terser-webpack-plugin")

module.exports = {
  // 为了避免代码块太长,这里省略啦其他配置项
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserWebpackPlugin(),
    ]
  },
}

另外,bundle.js文件中其实会存在一些项目依赖打包后的license许可注释,但是我们并不需要它存在,可以这样简单的配置一下terser-webpack-plugin插件。

new TerserWebpackPlugin({
  terserOptions: {
    format: {
      comments: false
    }
  },
  extractComments: false
})

html-inline-css-webpack-plugin

因为我们需要打包压缩生成好的css文件直接插入到index.html文件的headstyle,而不是使用link方式,所以我们还可以再使用一个插件html-inline-css-webpack-plugin

  1. 安装一下

    yarn add -D html-inline-css-webpack-plugin
    
  2. 配置一下

    const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default
    
    module.exports = {
      // 为了避免代码块太长,这里省略啦其他配置项
      plugins: [
        // ...省略了其他项
        new HTMLInlineCSSWebpackPlugin()
      ],
    }
    

完成需求的 webpack 配置

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default
const TerserWebpackPlugin = require("terser-webpack-plugin")

module.exports = {
  mode: 'production',
  entry: [
    './src/index.js',
    './assets/styles/main.css'
  ],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: ''
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin(),
    new HtmlWebpackPlugin({
      template: 'index.html'
    }),
    new HTMLInlineCSSWebpackPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
        loader: 'file-loader',
        options: {
          name: '[path][hash:18].[ext]',
          esModule: false
        }
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserWebpackPlugin()
    ]
  },
  resolve: {
    alias: {
      Assets: path.resolve(__dirname, 'assets/')
    }
  }
}

最后

🦢 Oops!太舒服了,现在只需要yarn build一下,部署需要的资源就在dist文件里满意的放着了。


版权声明:

Anand's Blog文章皆为站长Anand Zhang原创内容,转载请注明出处。

包括商业转载在内,注明下方要求的文章出处信息即可,无需联系站长授权。

请尊重他人劳动成果,用爱发电十分不易,谢谢!

请注明出处:

本文出自:Anand's Blog

本文永久链接:https://anandzhang.com/posts/frontend/19