构建工具基础:Webpack核心概念(Entry、Output、Loader、Plugin)

说实话,很多同学一上来就问我:「Webpack到底是个啥?」

我通常会打个比方:它就像一条流水线。你把原材料(源码)丢进去,它帮你加工、打包,最后输出成品(构建后的文件)。

嗯,今天我们就来拆解这条流水线的四个核心部件。我个人习惯把它们分成两组:入口与出口加载器与插件

Entry:一切从这里开始

Entry,就是打包的起点。Webpack会从这个文件出发,顺着依赖关系一路找到所有模块。

单入口 vs 多入口

单页面应用,通常一个入口就够了。比如:

module.exports = {
  entry: './src/index.js'
};

多页面应用,或者你想拆分包,可以用对象语法:

module.exports = {
  entry: {
    home: './src/home.js',
    about: './src/about.js'
  }
};

我在项目中遇到过一种情况:团队把第三方库和业务代码混在一个入口里。结果每次改一行业务代码,vendor缓存就失效了。后来我建议拆成多入口,配合SplitChunks,缓存命中率直接翻倍。

小技巧:如果你用单入口,但想提取公共依赖,记得配optimization.splitChunks。别一股脑全塞进一个bundle里。

Output:打包后的归宿

Output告诉Webpack:打包好的文件放哪,叫什么名字。

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js'
  }
};

这里有个坑,我曾经踩过——contenthash。它根据文件内容生成哈希值。内容变了,哈希才变。这样浏览器就能精准缓存未改动的文件。

你想想看,如果不用哈希,每次部署用户都得重新下载所有JS。那体验能好吗?

注意:多入口时,filename里必须用[name]占位符,否则会覆盖。我见过有人忘了加,结果最后一个入口覆盖了前面的,排查了半天。

Loader:让Webpack理解非JS文件

Webpack原生只懂JavaScript和JSON。那CSS、图片、TypeScript怎么办?

Loader就是翻译官。它把各种文件转成Webpack能处理的模块。

举个例子,处理CSS:

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

这里要注意顺序:从右到左,从下到上。css-loader先解析CSS里的import和url,然后style-loader把样式注入到DOM里。

我记得有一次,团队新同学把顺序写反了,结果样式死活不生效。他折腾了一下午,最后发现是loader顺序的问题。嗯,这种错误我也犯过,所以印象特别深。

常用Loader一览

Loader 用途
babel-loader 把ES6+转成ES5
ts-loader 处理TypeScript
file-loader 处理图片、字体等文件
url-loader 小文件转base64,减少请求
sass-loader 把SCSS/SASS转成CSS

避坑指南:我曾经用url-loader处理大图片,结果打包后的JS文件暴涨到5MB。后来加了limit限制,超过8KB的文件还是走file-loader。这样既减少了小请求,又避免了大文件内联。

Plugin:干Loader干不了的事

Loader只能处理模块转换。但打包优化、资源管理、环境注入这些事,得靠Plugin。

Plugin的威力在于:它能触及Webpack编译的整个生命周期。从开始编译到输出文件,每一步都能插手。

举个最常见的例子——HtmlWebpackPlugin:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};

它会自动生成HTML,并把打包后的JS/CSS注入进去。你不用手动改script标签了。

还有几个我常用的:

  • MiniCssExtractPlugin:把CSS从JS里抽出来,生成单独文件。对缓存和加载性能都有好处。
  • DefinePlugin:注入全局变量。比如区分开发环境和生产环境。
  • BundleAnalyzerPlugin:可视化分析打包体积。我每次做性能优化,第一步就是跑它看看哪个模块最大。

注意:Plugin和Loader的区别,说白了就是:Loader管「怎么转」,Plugin管「怎么做更多事」。别搞混了。

Entry + Output + Loader + Plugin 的协作流程

我习惯用一句话总结:

Entry告诉Webpack从哪开始,Loader处理沿途的各类文件,Plugin在关键时刻做额外操作,最后Output把结果写到指定位置。

举个例子,一个完整的配置大概长这样:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.[contenthash].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};

你看,每个部分各司其职。入口、出口、加载器、插件,缺一不可。

核心要点回顾

  • Entry:打包的起点,单页用字符串,多页用对象
  • Output:输出路径和文件名,善用contenthash做缓存
  • Loader:处理非JS文件,注意执行顺序从右到左
  • Plugin:扩展Webpack能力,覆盖编译全生命周期

说实话,这四个概念搞明白了,Webpack就算入门了。后面那些高级配置,都是在这四个基础上做文章。

下一章,我会带你实战配置一个完整的Webpack项目。到时候咱们再细聊优化的事。