28

学一点Webpack配置:基本配置

 4 years ago
source link: https://www.tuicool.com/articles/eQFvemz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

U77VJ36.jpg!web

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

iumii2I.png!web

点击这里可以查看大图

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

学点Webpack配置方面的知识

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

63Q3yyz.png!web

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

Webpack是什么

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

ieA3YvA.gif

那么,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 键执行下去:

reAvumy.png!web

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

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

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

QrMr6fe.png!web

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

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

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

r63myuj.png!web

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

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

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

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

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

QzuiIjU.png!web

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

nM7jMzE.png!web

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

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

MryEj2a.png!web

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

⇒  cd dist && touch index.html

并且将生成出来的 bundle.js 引入到新创建的 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>Hello Webpack (^_^) ~~~</title>
    </head>
    <body>
        <script src="./bundle.js"></script>
    </body>
</html>

在浏览器中打开 /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 的脚本输出的值:

QZbemmA.png!web

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

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

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

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

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

nmiqauu.png!web

这个时候,新创建的 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() 框:

3yEjamY.png!web

为了开发方便,不可能通过 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 就可以启动服务器:

3a67Jj6.png!web

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

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

    document.body.append(h1Ele);

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

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

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

aayM7j6.png!web

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

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

iQZJbu6.png!web

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

NJvy2e3.png!web

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

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(<App />, 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 (
            <h1>Hello Webpack and React! (^_^)</h1>
        )
    }
}

另外在 /src/ 新增一个模板文件 index.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>Hello Webpack (^_^) ~~~</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

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

IZrUfqq.png!web

首先我要告诉你的是 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 还是会报错:

nIbiYrn.png!web

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

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK