乐趣区

还学不会webpack看这篇

Webpack 已经流行好久了,但很多同学使用 webpack 时还是一头雾水,一下看到那么多文档、各种配置、各种 loader、plugin 立马就晕头转向了。我也不例外,以至于很长一段时间对 webpack 都是一知半解的状态。但是想要继续做好前端,webpack 是必须得跨过的一道坎,其实掌握 webpack 并不难,只是我们没有找到正确的方法。本文就是我自己在学习 webpack 时的一点心得体会,供大家参考。

什么是 webpack?

一句话概括:webpack 是一个 模块打包工具(module bundler)。重点在于两个关键词“模块”和“打包”。什么是模块呢?我们回顾一下曾经的前端开发方式,js 文件通过 script 标签静态引入,js 文件之间由于没有强依赖关系,如果文件 1 要用到文件 2 的某些方法或变量,则必须将文件 1 放到文件 2 后面加载。随着项目的增大,js 文件之间的依赖关系越发错综复杂,维护难度也越来越高。这样的困境驱使着前端工程师们不断探索新的开发模式,从后端、app 的开发模式中我们获得灵感,为什么不能引入“模块”的概念让 js 文件之间可以相互引用呢?模块 1 要使用模块 2 的功能,只需要在该模块 1 中明确引用模块 2 就行了,而不用担心它们的排列顺序。基于这种理念,CommonJS 和 AMD 规范被创造了出来,然后有了 require.js、system.js 这样的前端模块加载工具和 node 的模块系统,直到现在流行的 es6 module。

模块的引入解决了文件之间依赖引用的问题,而打包则解决了文件过多的问题。当项目规模增大,模块的数量数以千计,浏览器如果要加载如此多的文件,页面加载的速度势必会受影响,而 bundler 可以把多个关联的文件打包到一起从而大量减少文件的数量提高网页加载性能。提供模块化的开发方式和编译打包功能就是 webpack 的核心,其他很多功能都围绕它们展开。

核心概念

Module(模块)

对于 webpack,模块不仅仅是 javascript 模块,它包括了任何类型的源文件,不管是图片、字体、json 文件都是一个个模块。Webpack 支持以下的方式引用模块:

  • ES2015 import 方法
  • CommonJs require() 方法
  • AMD definerequire 语法
  • css/sass/less 文件中的 @import 语法
  • url(...)<img src=...> 中的图片路径

Dependency Graph(依赖关系图)

所谓的依赖关系图是 webpack 根据每个模块之间的依赖关系递归生成的一张内部逻辑图,有了这张依赖关系图,webpack 就能按图索骥把所有需要模块打包成一个 bundle 文件了。

Entry(入口)

绘制依赖关系图的起始文件被称为 entry。默认的 entry 为 ./src/index.js,或者我们可以在配置文件中配置。entry 可以为一个也可以为多个。

单个 entry:

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

或者

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

多个 entry,一个 chunk

我们也可以指定多个独立的文件为 entry,但将它们打包到一个 chunk 中,此种方法被称为 multi-main entry,我们需要传入文件路径的数组:

module.exports = {entry: ['./src/index.js', './src/index2.js', './src/index3.js']
}

但是改种方法的灵活性和扩展性有限,因此并不推荐使用。

多个 entry,多个 chunk

如果有多个 entry,并且每个 entry 生成对应的 chunk,我们需要传入 object:

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  }
};

这种写法有最好的灵活性和扩展性,支持和其他的局部配置(partial configuration)进行合并。比如将开发环境和生产的配置分离,并抽离出公共的配置,在不同的环境下运行时再将环境配置和公共配置进行合并。

Output(出口)

有了入口,对应的就有出口。顾名思义,出口就是 webpack 打包完成的输出,output 定义了输出的路径和文件名称。Webpack 的默认的输出路径为 ./dist/main.js。同样,我们可以在配置文件中配置 output:

module.exports = {
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  }
};

多个 entry 的情况

当有多个 entry 的时候,一个 entry 应该对应一个 output,此时输出的文件名需要使用替换符(substitutions)声明以确保文件名的唯一性,例如使用入口模块的名称:

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {filename: '[name].js',
    path: __dirname + '/dist'
  }
}

最终在 ./dist 路径下面会生成 app.jssearch.js 两个 bundle 文件。

Loader(加载器)

Webpack 自身只支持加载 js 和 json 模块,而 webpack 的理念是让所有的文件都能被引用和加载并生成依赖关系图,所以 loader 出场了。Loader 能让 webpack 能够去处理其他类型的文件(比如图片、字体文件、xml)。我们可以在配置文件中这样定义一个 loader:

webpack.config.js

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

其中 test 定义了需要被转化的文件或者文件类型,use定义了对该文件进行转化的 loader 的类型。该条配置相当于告诉 webpack 当遇到一个 txt 文件的引用时(使用 require 或者 import 进行引用),先用 raw-loader 转换一下该文件再把它打包进 bundle。

还有其他各种类型的 loader,比如加载 css 文件的 css-loader,加载图片和字体文件的file-loader,加载 html 文件的html-loader,将最新 JS 语法转换成 ES5 的babel-loader 等等。完整列表请参考 webpack loaders。

Plugin(插件)

Plugin 和 loader 是两个比较混淆和模糊的概念。Loader 是用来转换和加载特定类型的文件,所以 loader 的执行层面是单个的源文件。而 plugin 可以实现的功能更强大,plugin 可以监听 webpack 处理过程中的关键事件,深度集成进 webpack 的编译器,可以说 plugin 的执行层面是整个构建过程。Plugin 系统是构成 webpack 的主干,webpack 自身也基于 plugin 系统搭建,webpack 有丰富的内置插件和外部插件,并且允许用户自定义插件。官方列出的插件有 这些。

与 loader 不同,使用 plugin 我们必须先引用该插件,例如:

const webpack = require('webpack'); // 用于引用 webpack 内置插件
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 外部插件

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

实践

了解 webpack 的基本概念之后,我们通过实践来加深理解。接下来,我们使用 webpack 搭建一个简易的 react 脚手架。

创建项目

首先创建 react-webpack-starter 项目并使用 npm init 初始化。

安装依赖

安装 react

npm i react react-dom

安装 webpack 相关

npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin style-loader css-loader

安装 webpack-cli 后可以在命令行中执行 webpack 命令;webpack-dev-server提供了简易的 web 服务器,并且在修改文件之后自动执行 webpack 的编译操作并自动刷新浏览器,省去了重复的手动操作;html-webpack-plugin用于自动生成 index.html 文件,并且在 index.html 中自动添加对 bundle 文件的引用;style-loadercss-loader 用于加载 css 文件。

安装 babel 相关

由于 react 中使用了 class, import 这样的 es6 的语法,为了提高网站的浏览器兼容性,我们需要用 babel 转换一下。

npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader 

其中 @babel/core 是 babel 的核心模块,包含了 babel 的核心功能;@babel/preset-env支持转换 ES6 以及更新的 js 语法,并且可根据需要兼容的浏览器类型选择加载的 plugin 从而精简生成的代码;@babel/preset-react包含了 babel 转换 react 所需要的 plugin;babel-loader是 webpack 的 babel 加载器。

配置 webpack

在项目根目录下面新建webpack.config.js,内容如下:

webpack.config.js

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

module.exports = {
  entry: './src/index.js',
  output: {path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_module/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'] // 注意排列顺序,执行顺序与排列顺序相反
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
}

其中 HtmlWebpackPlugin 使用自定义的模版来生成 html 文件,模版的内容如下:

./src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>My React App</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

配置 babel

在项目根目录下面新建 .babelrc 文件,配置我们安装的两个 babel preset:

.babelrc

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

生成 react 应用根节点

./src/index

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(<App />, document.getElementById('app'));

./src/component/App.js

import React, {Component} from 'react';
import './App.css';

export default class App extends Component {render() {
    return (
      <div>
        my react webpack starter
      </div>
    )
  }
}

./src/components/App.css

body{
  font-size: 60px;
  font-weight: bolder;
  color: red;
  text-transform: uppercase;
}

配置 package.json

最后,在 package.json 文件里面加上两个 scripts,用来运行开发服务器和打包:

package.json

"scripts": {
  "start": "webpack-dev-server --mode development --open --hot",
  "build": "webpack --mode production"
}

注意,我们启用了 webpack-dev-server 的模块热更新功能(HMR),进一步提高我们的开发效率。

到此一个最简版本的 react 脚手架就搭建完成了,我们运行一下看看效果:

是不是没有想象中的那么难呢?当然 webpack 还有很多其他的功能和特性需要掌握,希望在参考本文之后大家进一步的学习更加顺利 ????。

本文 demo 地址:https://github.com/MudOnTire/…

退出移动版