学一点Webpack配置:基本配置

这两天朋友圈流行这么一张图:

多么形象的展示了前端学习的曲线图。真可谓是一言难尽呀,现在的前端真不好学,乱而杂。如果你要是再看看@Kamran Ahmed整理的 2017年2018年2019年 现代Web开发者要掌握的 Roadmap ,估计更会泪崩:

点击这里可以查看大图

现状是如此,未来可能会更混乱,但我们不应该去抱怨,应该更应该保持一颗爱学习的心,继续往前行走。

学点Webpack配置方面的知识

Webpack 是构建工具中必不可少的一部分:

作为现代Web开发者就需要对Webpack有所了解,哪怕掌握的不够深入,略知皮毛也对我们自己的工作或学习都是有所帮助的。比如说吧,前段时间折腾React环境下的CSS Modules,就是因为自己对Webpack不了解,有些坑踩了无法立刻解决,就算借助互联网,解的也是知半解(而且现在技术更新太快,网上有些教程根本走不通,不踩不知道,一踩只有泪)。正因为这个原因,促使自己去了解Webpack更多的知识。接下来的内容是一些基础,主要会介绍怎么用Webpack来构建自己的开发环境,感兴趣的请继续往下阅读。

Webpack是什么

一直以来,在我自己的印象和理解中,都认为 Webpack是一个构建工具 。主要用来构建开发的工程体系。但从其官网来看,告诉我Webpack是一个模块Bundler(捆绑器):

那么,Webpack到底是一个构建工具(或者说一个构建系统)还是一个模块Bundler(捆绑器)呢?答案是:

Webpack既是一个构建系统,也是一个捆绑器!

Webpack不是先构建你的资源(Assets),然后再bundle你的模块,它把你的资源本身就当做是一个模块。这些模块可以被 导入修改操作 等,最后才被打包到你最后的bundle。

简单地说,Webpack其最核心的功能就是 解决模板之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并成一个JS文件(比如 bundle.js 。这个整个过程也常常被称为是 模块打包 。换句话说,Webpack是一个指令集合的配置文档,然后通过配置好的这些指令去驱动程序做一些指令要求要做的事情。而这些动作都是通过自己写的规则去做编译,而且通过JavaScript的引入( import )语法让Webpack知道需要它帮忙编译什么东西(比如Pug、Sass等等)。所以我们始终会有一个入口文件(比如 index.js )注入那些Preprocess,让那些Preprocess可以通过这些入口文件的JavaScript让Webpack去根据相关的配置指令编译它,然后打包到一个出口文件中,比如 bundles.js

为什么要用Webpack

一直以来,在开发Web页面或Web应用程序的时候,都习惯性的将不同资源放置在不同的文件目录之中,比如图片放置在 images (或 img )下,样式文件放置在 styles (或 css )中,脚本文件放在 js 和模板文件放置在 pages 中。一直以来,发布的时候都会一次性的将所有资源打包发布,不管这些资源用到了还是没用到(事实上很多时候自己都分不清楚哪资源被使用)。用一句话来描述就是: 依赖太复杂,太混乱,无法维护和有效跟踪 。比如哪个样式文件引用了 a.img ,哪个样式文件引用了 b.img ;另外页面到底是引用了 a.css 呢还是 b.css 呢?

而Webpack这样的工具却能很多好的解决它们之间的依赖关系,使其打包后的结果能运行在浏览器上。其目前的工作方式主要被分为两种:

.js

相比于Parcel、Rollup具有同等功能的工具而言,Webpack还具有其他的优势:

  • Webpack支持多种模块标准 :这对于一些同时使用多种模块标准的工程非常有用,Webpack会帮我们处理好不同类型模块之间的依赖关系
  • Webpack有完备的代码分割解决方案 :它可以分割打包后的资源,首屏只加载必要的部分,不太重要的功能放到后面动态地加载
  • Webpack可以处理各种类型的资源 :除了JavaScript之外,Webpack还可以处理样式、模板、图片等资源。开发者要做的只是将之些资源导入,而无需关注其他

另外,Webpack还拥有一个强大的社区。这也是其受开发者青眯的原因之一。接下来,我们还是实际一点,动手来撸码。

从零开始构建你自己的开发环境

为了更好的理解Webpack能帮我们做什么,我打算从零开始构建一个属于自己的开发环境。可能在后面的内容中会涉及到很多关键词,比如Webpack、loaders、Babel、sourcemaps、React、TypeScript,CSS Modules等等。接下来一步一步的学习中会了解到这些单词和相关技术。

后面会一步一步的带大家如何使用Webpack配置适合自己的开发环境,会涉及到一些相关技术,但不会深入到具体技术细节中。

在写这篇文章所具备的环境是:Node是 v10.9.0 ,NPM是 v6.9.2 ,Webpack是 v4.34.0 ,React是 v16.8.6 ,TypeScript是 v3.4.4 ,Sass是 v.6.9.0 ,PostCSS是 v.6.9.0 等。接下来的内容会以配置React + TypeScript + CSS Modules + PostCSS为主线,从零开始一个项目。另外,接下来的内容会以不同分支的形式将 代码放置在Github上 。感兴趣的可以直接将仓库克隆下来,切换到对应步骤的分支,查看代码。

Step01:初始化项目

请将Git分支切换到 step1 分支查看代码

首先在你的本地创建一个项目,比如我这里创建了一个 webpack-sample 项目:

⇒ mkdir webpack-sample && cd webpack-sample

进入到新创建的项目目录下,执行 npm init 或者 npm init -y 命令来初始化项目,执行完该命令之后,在你的命令终端会看到类似下图这样的命令询问,你可以根据你自己的需要去输入你想要的内容,或者一路 Enter 键执行下去:

此时你的项目根目录下会增加一些文件和文件夹:

|--webpack-sample/
|----node_modules/
|----package.json
|----package-lock.json

其中 package.json 文件里将包含一些项目信息:

注意,这个文件随着后面的步骤完成,会增加更多的内容。

package-lock.json 文件是当 node_modules/package.json 发生变化时自动生成的文件,它的主要功能是 确定当前安装的包的依赖,以便后续重新安装的时候生成相同的依赖,而忽略项目开发过程中有些依赖已经发生的更新

在Step01中,我们对 package.json 文件只做一个修改,删除 "main": "index.js" 入口,并添加 "private":true 选项,以便确保安装包是私有的,这样可以防止意外发布你的代码。

Step02:安装Webpack和初始配置Webpack

请将分支切换到 step2 查看代码

在这一步,先来安装Webpack。执行下面的命令安装Webpack配置所需要的包:

⇒ npm i webpack webpack-cli webpack-dev-server -D

此时打开 package.json 文件,你会发现在文件中有一个新增项 devDependencies

{
    // 其他项信息在这省略,详细请查看该文件

    "devDependencies": {
        "webpack": "^4.35.0",
        "webpack-cli": "^3.3.5",
        "webpack-dev-server": "^3.7.2"
    }
}

注意,在命令终端使用 npm i 安装依赖关系时,如果带后缀 -D (或 --save-dev 安装的包会记录在 "devDependencies" 下;如果使用 --save 后缀(我们后面会用到)安装的包会记录在 "dependencies" 下。两者的区别是:

  • "devDependencies" dev 开发时的依赖包
  • dependencies :程序运行时的依赖包

为了验证Webpack是否能正常工作,这个时候我们需要创建一些新的文件。在 webpack-sample 根目录下创建 /src 目录,并且在该目录下创建一个 index.js 文件:

⇒ mkdir src && cd src && touch index.js

执行完上面的命令,你会发现你的项目目录结构变成下图这样:

我们在新创建的 /src/index.js 文件下添加一行最简单的JavaScript代码:

console.log("Hello, Webpack!(^_^)~~")

保存之后回到命令终端,第一次执行有关于Webpack相关的命令:

⇒ npx webpack src/index.js --output dist/bundle.js

执行完上面的命令后,如果看到下图这样的结果,那么要恭喜你,Webpack的安装已经成功,你可以在你的命令终端执行有关于Webpack相关的命令:

回到项目中,会发现项目根目下自动创建了一个 /dist 目录,而且该目录下包含了一个 bundle.js 文件:

执行完上面的命令之后,可以看到有相关的警告信息。那是因为 Webpack4增加了 mode 属性 ,用来表示不同的环境。 mode 模式具有 developmentproductionnone 三个值,其默认值是 production 。也就是说,在执行上面的命令的时候,我们可以带上相应的 mode 属性的值,比如说,设置 none 来禁用任何默认行为:

⇒  npx webpack src/index.js --output dist/bundle.js --mode none

执行到这里,只知道我们可以运行Webpack相关命令。并不知道 /src/index.js 的代码是否打包到 /dist/bundle.js 中。为此,我们可以在 /dist 目录下创建一个 index.html

⇒  cd dist && touch index.html

并且将生成出来的 bundle.js 引入到新创建的 index.html


    
        
        
        
        Hello Webpack (^_^) ~~~
    
    
        
    

在浏览器中打开 /dist/index.html ,或者在命令行中执行:

⇒  npm i -g http-server
⇒  http-server dist

http-server 是一个启动服务器的 npm 包,执行上面的命令之后,就可以在浏览器中访问 http://127.0.0.1:8080/ (访问的 /dist/index.html ),在浏览器的 console.log 控制台中,可以看到 src/index.js 的脚本输出的值:

上面的过程足以验证,你的Webpack能正常的工作了。

不过,当你需要构建的东西越复杂,需要的标志就会越多。在某种程度上说,就会变得难以控制。这个时候我们就需要一个文件来管理这些配置。接下来我们需要创建一个 webpack.config.js 这样的一个文件,用来配置Webpack要做的事情。注意,这个文件是一个 node.js 文件,所以你可以在任何节点文件中执行任何你能够执行的操作。你也可以写成 json 文件,但是node文件更强大一些。

首先们先创建Webpack的配置文件,在 webpack-sample 根目录下创建一个 /build 目录,然后在该目录下添加一个名为 webpack.config.js 文件:

⇒  mkdir build && cd build && touch webpack.config.js

执行完上面的命令之后,你会发现你的项目文件目录结构变成下面这样了:

这个时候,新创建的 webpack.config.js 文件里面是一片空白,它就是Webpack的配置文件,将会导出一个对象的JavaScript文件。我们需要在这个文件中添加一些配置:

var webpack = require('webpack');
var path = require('path');
var DIST_PATH = path.resolve(__dirname, '../dist');  // 声明/dist的路径

module.exports = {
    // 入口JS路径
    // 指示Webpack应该使用哪个模块,来作为构建其内部依赖图的开始
    entry: path.resolve(__dirname,'../src/index.js'),


    // 编译输出的JS入路径 
    // 告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件
    output: {
        path: DIST_PATH,        // 创建的bundle生成到哪里
        filename: 'bundle.js',    // 创建的bundle的名称
    },

    // 模块解析
    module: {

    },

    // 插件
    plugins: [

    ],

    // 开发服务器
    devServer: {

    }
}

Webpack配置是标准的 Node.js CommonJS模块 ,它通过 require 来引入其他模块,通过 module.exports 导出模块,由Webpack根据对象定义属性进行解析。

上面很简单,到目前为止只通过 entry 设置了入口起点 ,然后通过 output 配置了打包文件输出的目的地和方式 。你可能也发现了,在配置文件中还有 modulepluginsdevServer 没有添加任何东西。不需要太急,后面会一步一步带着大家把这里的内容补全的,而且随着配置的东西越来越多,整个 webpack.config.js 也会更变越复杂。

完成 webpack.config.js 的基础配置之后,回到 package.json 文件,并在 "scripts" 下添加 "build": "webpack --config ./build/webpack.config.js"

// package.json

{
    // ...

    "scripts": {
        "build": "webpack --config ./build/webpack.config.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
}

这样做的,为让我们直接在命令终端执行相关的命令就可以实现相应的功能。比如上面配置的 build ,在命令终端执行:

⇒  npm run build

上面的命令执行的效果前面提到的 npx webpack src/index.js --output dist/bundle.js --mode none 等同。同样有警告信息,主要是 mode 的配置没有添加。在上面的配置中添加:

{
    "scripts": {
        "build": "webpack --config ./build/webpack.config.js --mode production",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
}

再次执行 npm run build ,不会再有警告信息。你可以试着修改 /src/index.js 的代码:

alert(`Hello, Webpack! Let's Go`);

重新编译之后,打开 /dist/index.html 你会发现浏览器会弹出 alert() 框:

为了开发方便,不可能通过 http-server 来启用服务。我们可以把这部分事件放到开发服务器中来做,对应的就是 devServer ,所以我们接着在 webpack.config.js 中添加 devServer 相关的配置:

// webpack.config.js

// 开发服务器
devServer: {
    hot: true,                  // 热更新,无需手动刷新
    contentBase: DIST_PATH,     // 
    host: '0.0.0.0',            // host地址
    port: 8080,                 // 服务器端口
    historyApiFallback: true,   // 该选项的作用所用404都连接到index.html
    proxy: {
        "/api": "http://localhost:3000" // 代理到后端的服务地址,会拦截所有以api开头的请求地址
    }
}

有关于 devServer 更详细的配置参数描述, 可以查阅读Webpack官网相关文档

build 类似,需要在 package.jsonscripts 中添加相关的命令:

// package.json

"scripts": {
    "build": "webpack --config ./build/webpack.config.js --mode production",
    "dev": "webpack-dev-server --config ./build/webpack.config.js --mode development --open",
    "test": "echo \"Error: no test specified\" && exit 1"
},

保存所有文件,在命令行中执行 npm run dev 就可以启动服务器:

你可以验证一下,修改 /src/index.js

document.addEventListener('DOMContentLoaded', () => {
    const h1Ele = document.createElement('h1')

    document.body.append(h1Ele);

    h1Ele.innerText = 'Hello Webpack (^_^)'

    h1Ele.style.color = '#f46';
})

保存该文件之后,浏览器会立刻刷新,你将看到修改之后的变化:

Step03: 优化Webpack配置

请将分支切换到 step3 查看代码

Step02 中,开发和生产环境相关的配置都集成在 webpack.config.js 一个文件中。为了更好的维护代码,在 Step03 中做一些优化。把 webpack.config.js 拆分成三个部分:

  • 公共配置 :把开发和生产环境需要的配置都集中到公共配置文件中,即 webpack.common.js
  • 开发环境配置 :把开发环境需要的相关配置放置到 webpack.dev.js
  • 生产环境配置 :把生产环境需要的相关配置放置到 webpack.prod.js

先在 /build 目录下创建上面提到的三个配置文件。在命令终端执行下面的命令即可:

⇒  cd build && touch webpack.common.js webpack.dev.js webpack.prod.js

这个时候,整个项目目录结构变成下图这样:

Step02 中遗留下来的 webpack.config.js 文件将会从 /build 目录中移除。

为了更好的管理和维护这三个文件,需要安装一个 webpack-merge 插件 :

⇒  npm i webpack-merge -D

执行完上面的命令之后, package.json 文件中的 devDependencies 会增加 webpack-merge 相关的配置:

// package.json

{
    //... 省略的信息请查看原文件
    "devDependencies": {
        "webpack": "^4.35.0",
        "webpack-cli": "^3.3.5",
        "webpack-dev-server": "^3.7.2",
        "webpack-merge": "^4.2.1"
    }
}

接下来分别给 webpack.common.jswebpack.dev.jswebpack.prod.js 文件添加相关的配置:

Webpack公共配置

在公共配置文件 webpack.common.js 文件中添加相应的配置:

const webpack = require('webpack');
const path =  require('path');
const DIST_PATH = path.resolve(__dirname, '../dist/');  // 声明/dist的路径

module.exports = {
    // 入口JS路径
    // 指示Webpack应该使用哪个模块,来作为构建其内部依赖图的开始
    entry: path.resolve(__dirname,'../src/index.js'),


    // 编译输出的JS入路径 
    // 告诉Webpack在哪里输出它所创建的bundle,以及如何命名这些文件
    output: {
        path: DIST_PATH,        // 创建的bundle生成到哪里
        filename: 'bundle.js',    // 创建的bundle的名称
    },

    // 模块解析
    module: {

    },

    // 插件
    plugins: [

    ]
}

Webpack开发环境配置

接着给Webpack开发环境配置文件 webpack.dev.js 添加下面的相关配置:

const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');

const commonConfig = require('./webpack.common.js');

const DIST_PATH = path.resolve(__dirname, '../dist/');  // 声明/dist的路径

module.exports = merge(commonConfig, {
    mode: 'development', // 设置webpack mode的模式

    // 开发环境下需要的相关插件配置
    plugins: [

    ],

    // 开发服务器
    devServer: {
        hot: true,                  // 热更新,无需手动刷新
        contentBase: DIST_PATH,     // 
        host: '0.0.0.0',            // host地址
        port: 8080,                 // 服务器端口
        historyApiFallback: true,   // 该选项的作用所用404都连接到index.html
        proxy: {
            "/api": "http://localhost:3000" // 代理到后端的服务地址,会拦截所有以api开头的请求地址
        }
    }
})

Webpack生产环境配置

继续给Webpack生产环境配置文件 webpack.prod.js 添加相关配置:

const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');

const commonConfig = require('./webpack.common.js');

module.exports = merge(commonConfig, {
    mode: 'production', // 设置Webpack的mode模式

    // 生产环境下需要的相关插件配置
    plugins: [

    ],
})

上面的配置信息只是将 Step02webpack.config.js 分成三个文件来配置,随着后续添加相应的配置信息,那么这三个文件中的配置信息会越来越多,也会越来越复杂。

修改完Webpack的配置之后,对应的 package.json 中的 scripts 中的信息也要做相应的调整:

// package.json
{
    // ... 其他配置信息请查看原文件
    "scripts": {
        "build": "webpack --config ./build/webpack.prod.js --mode production",
        "dev": "webpack-dev-server --config ./build/webpack.dev.js --mode development --open",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
}

这个时候重新在命令终端执行

// 执行build命令,重新打包
⇒  npm run build

// 执行dev命令
⇒  npm run dev

这仅仅是最基础部分的优化,因为我们的配置还是最简单的,后续我们添加了别的配置之后,也会在相应的步骤做相应的优化。

Step04: 配置React开发环境

请将分支切换到 step4 查看代码

经过前面三步,我们完成了Webpack的基本配置,知道文件入口,出口,打包以及开发,生产等环境。接下来,我们来给工程配置React相关的环境。

React的环境需要先安装 reactreact-dom 。所以先在命令终端中执行下面的命令:

⇒  npm i react react-dom --save

执行完上面的命令之后,在 package.json 文件中的 dependencies 增加了 ractreact-dom 相应的信息:

// package.json
{
    "dependencies": {
        "react": "^16.8.6",
        "react-dom": "^16.8.6"
    },
}

为了验证React相关的环境是否能正常工作,将 /src/index.js 中的内容做一些修改:

// /src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './components/App'

ReactDOM.render(, document.getElementById('root'))

在这个 index.js 中引用了 App 组件。所以我们在 /src 目录下新增 components/ 目录,并在该目录下新增 App.js

// src/components/App.js

import React from 'react';

export default class App extends React.Component {
    render() {
        return (
            

Hello Webpack and React! (^_^)

) } }

另外在 /src/ 新增一个模板文件 index.html





    
    
    
    Hello Webpack (^_^) ~~~


    

这个时候你在命令终端不管是执行 npm run build 还是 npm run dev 都无法正常运行,会报错:

首先我要告诉你的是 Step04 这一步的操作并没有任何问题,主要是在编译的过程中缺少必要的东西。那就是Babel相关的配置。接下来的 Step05 将会添加Babel相关的配置。

Step05:添加Babel相关的配置

请将分支切换到 step5 查看代码

Step04 中会失败主要是因为Webpack只识别JavaScript文件,而且只能编译ES5。实际上ES6(甚至后面要说的JSX),Webpack它根本不认识。那么要解决这个问题,就需要借助Babel来处理。先安装需要的插件:

⇒  npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-transform-modules-commonjs @babel/preset-react -D

⇒  npm i @babel/runtime --save

执行完上面的命令之后, package.json 文件在 dependenciesdevDependencies 添加了新的配置信息:

// package.json

{
    // ...省略的信息可以查看原文件

    "dependencies": {
        "@babel/runtime": "^7.4.5",
        "react": "^16.8.6",
        "react-dom": "^16.8.6"
    },
    "devDependencies": {
        "@babel/core": "^7.4.5",
        "@babel/plugin-transform-modules-commonjs": "^7.4.4",
        "@babel/plugin-transform-runtime": "^7.4.4",
        "@babel/preset-env": "^7.4.5",
        "@babel/preset-react": "^7.0.0",
        "babel-loader": "^8.0.6",
        "webpack": "^4.35.0",
        "webpack-cli": "^3.3.5",
        "webpack-dev-server": "^3.7.2",
        "webpack-merge": "^4.2.1"
    }
}

接着在 webpack-sample 根目录下创建 .babelrc 文件来配置Babel:

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "targets": {
                    "browsers": [
                        "> 1%",
                        "last 5 versions",
                        "ie >= 8"
                    ]
                }
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-transform-runtime",
        "@babel/plugin-transform-modules-commonjs"
    ]
}

有关于Babel更详细的配置 可以点击这里阅读 ,这里不再做过多的阐述。

最后在 webpack.common.js 配置文件中的 module 中添加 rules 来处理 .js.jsx 文件,这也是我们添加的第一个有关于Webpack的Loader相关的东西:

// webpack.common.js

module.exports = {
    // ... 省略的信息查看原文件代码

    // 模块解析
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            }
        ]
    },
}

这个时候执行 npm run build 可以正常的执行,但是执行 npm run dev 还是会报错:

执行 npm run dev 不成功是因为我们的模板文件没有自动插入到 /dist 目录下。为了让 /src/ 目录下的模板文件 index.html 能自动编译到 /dist 目录下,并且所有的 .js 引用能自动插入到 index.html 中。我们需要使用Webpack的两个插件:

  • 生成 index.html 文件,并且自动插入到 /d