小伙子被说不懂webpack!怒看这篇文章

572 阅读21分钟

前言

现如今不论是工作中遇到的项目开发,还是自己的私人项目,webpack已经成为开发的核心骨干,对于webpack的了解几乎是所有前端开发人员必不可少的知识。

webpack官方的文档对于初学webpack的人来说是比较为难的,由于webpack功能的强大,具有一定的复杂性,文档也是很难做到面面俱到。

下面我会对自己学习webpack做一个总结,可以说是一个初学指南,希望可以给更多想学习webpack的人有一个了解webpack的指引。

本章会带领大家搭建基本的前端开发环境,然后详细介绍webpack的基础常用配置

本文不会仅仅局限于初级,目前已更新至webpack优化前端项目,GITHUB地址:github.com/Jason9708/w… , 如果能帮到各位学习webpack,希望能点个赞(小弟有个理想,当掘金优秀作者)


Webpack 基本概念

webpack 本质上是一个打包工具,它会根据代码的内容解析模块依赖,帮助我们把多个模块的代码打包。

webpack 会把我们项目中使用到的多个代码模块(可以是不同文件类型),打包构建成项目运行仅需要的几个静态文件。webpack 有着十分丰富的配置项,提供了十分强大的扩展能力,可以在打包构建的过程中做很多事情。

入口(Enrty)

在多个代码模块中会有一个js文件作为webpack构建的入口。webpack通过读取这个文件,从它开始进行解析依赖,然后打包。(默认入口文件为./src/index.js)

根据项目的不同需求,可能是单页面应用,也可能是多页面应用,如果是单页面应用,入口就只有一个;而如果项目比较大型,是多页面应用,那么入口通常会设置多个,一个页面对应一个入口

以下为配置入口的例子

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

// 多页面
module.exports = {
    entry: {
        application1: './src/application1.js',
        application2: './src/application2.js',
        // ...
    }
}

// 使用数组来对多个文件进行打包
module.exports = {
    entry: {
        main: [
            './src/application1.js',
            './src/application2.js
        ]
    }
}

// 最后这个例子,可以理解为多个文件作为一个入口,webpack会解析两个文件的依赖然后进行打包

Loader

webpack通过loader提供了一种处理多种文件格式的机制。我们可以将loader理解为是转换器,负责把某种文件格式的内容转换成webpack可以支持打包的模块

Ps:在没有添加额外插件的情况下,webpack 会默认把所有依赖打包成 js 文件,如果入口文件依赖一个 .hbs 的模板文件以及一个 .css 的样式文件,那么我们需要 handlebars-loader 来处理 .hbs 文件,需要 css-loader,style-loader 来处理 .css 文件,最终把不同格式的文件都解析成 js 代码,以便打包后在浏览器中运行。

如何配置loader规制呢? 当我们需要使用不同的loader来解析不同类型的文件时,在module.rules下配置规则

举个爪子:使用Babel来处理.js文件

module: {
    //...
    rules: [
        {
            test:/\.jsx?/,  // test匹配文件路径的正则表达式,通常都是匹配文件类型后缀
            include: [
                path.resolve(__dirname,'src') // 指定哪些路径下的文件需要经过loader处理,通常都是src下的文件
            ],
            use: 'babel-loader', // 指定使用的 loader
        }
    ]
}

loader支撑了webpack处理文件的功能,所以它是比较重要,也比较复杂的,想要玩好webpack,就要玩好loader

Plugin

webpack的构建流程中,plugin用于处理更多其他的构建任务(模块代码转换的工作由loader处理,而其他任何工作都可以交由plugin来完成)。我们依据需求来添加所需的plugin,实现我们所需要的功能(Such as HtmlWebpackPlugin 可以生成创建html入口文件)

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

module.exports = {
    // ...
    plugins: [
        new HtmlWebpackPlugin
    ]
}

plugin可以作用于webpack的整个构建流程,可以在流程的每一个步骤中定制自己的构建需求。在必须时,我们还可以在webpack的基础上自己开发plugin来适应我们的需求

输出 output

输出即webpack最终构建出来的静态文件。可以通过output来配置构建结果的文件名、路径等

module.exports = {
    //...
    output: {
        path:path.resolve(__dirname,'dist'), // 根目录下的dist
        filename:'index.js'
    }
}

// 可以配置不同入口的不同输出文件
module.exports = {
    entry: {
        application1: './src/application1.js',
        application2: './src/application2.js',
    },
    output: {
        filename: '[name].js',
        path: __dirname + '/dist',
    }
}

❗ 如果没有进行配置,默认创建的输出内容就是./dist/main.js

一个简单的webpack配置

webpack运行时默认读取项目下的 webpack-config.js,所以我们手动创建一个webpack.config.js文件

webpack 的配置其实是一个 Node.js 的脚本,这个脚本对外暴露一个配置对象,webpack 通过这个对象来读取相关的一些配置。因为是Node.js脚本,所以可玩性非常高,你可以使用任何的 Node.js 模块,如 path 模块,或者第三方的模块也可以。

const path = require('path')
const UglifyPlugin = require('uglifyjs-webpack-plugin') // 用于缩小(压缩)JsW文件

module.exports = {
    entry: './src/index.js', // 入口

    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'output.js'
    },

    module: {
        rules: [{
            test: /\.jsx/,
            include: [
                path.resolve(__dirname, 'src')
            ],
            use: 'babel-loader'
        }]
    },

    // 代码模块路径解析的配置
    resolve: {
        modules: [
            "node_modules",
            path.resolve(__dirname, 'src')
        ],

        extensions: [".wasm", ".mjs", ".js", ".json", ",jsx"],
    },

    plugins: [
        new UglifyPlugin()
        // 使用 uglifyjs-webpack-plugin 来压缩 JS 代码
        // 如果你留意了我们一开始直接使用 webpack 构建的结果,你会发现默认已经使用了 JS 代码压缩的插件
        // 这其实也是我们命令中的 --mode production 的效果,后续的小节会介绍 webpack 的 mode 参数
    ]
}

前端社区三大框架基于 webpack 的脚手架工具

  • create-react-app
  • angular/devkit/build-webpack
  • vue/cli

搭建基本的前端开发环境

基础前端开发环境需求分析

  • 构建我们发布需要的HTML,JS,CSS文件
  • 使用CSS预处理器来编写样式
  • 处理和压缩图片
  • 使用Babel支持ES6语法
  • 本地提供静态服务方便开发调式

HTML

通常来说,一个前端项目都是从一个页面(即HTML)出发的,最简单的方法是,创建一个.html文件,使用script引用构建好的.js文件

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

如果我们的文件名或者路径会变化,例如使用[hash]来进行命名,那么最好是将HTML引用路径和我们的构建结果关联起来,就会使用html-webpack-plugin

html-webpack-plugin是独立的依赖包,所以在使用之前要安装它。

npm install html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    // ...
    plugins: [
        new HtmlWebpackPlugin
    ]
}

这里配置之后,构造时html-webpack-plugin会为我们创建.html文件,其中会引用构建出来的html-webpack-plugin,传递一个写好的.html模板

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // 配置输出文件名和路径
      template: path.join(__dirname,'./src/index.html'),   //指定模板页面
    }),
  ],
}

html-webpack-plugin插件生成的内存中的页面会帮我们创建并正确引用了打包编译生成的index.js<script></script>

Css

当我们希望使用webpack对我们所编写的css进行构建,则需要在配置中引入相关loader来解析和处理.css文件(需要用到style-loader,css-loader,两者都是依赖包,需要先进行安装)

module.exports = {
    rules: [
        {
            test: /\.css/,
            include: [
                path.resolve(__dirname,'src')
            ],
            use:[
                'style-loader',
                'css-loader'
            ]
        }
    ]
}

css-loader,style-loader的作用

  • css-loader:负责解析css代码,处理css中的依赖,例如@import以及url()等引用外部文件的声明
  • style-loader:将css-loader解析出来的结果转变为js代码,运行时会插入到<style></style>中来让css代码运作

经过css-loader,style-loader的处理,css代码就会转变为js代码,最后一起打包出来。

如果需要将.css文件单独分离,需要用到extract-text-webpack-plugin插件(这里不对其进行解释)

const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/,
        // 因为这个插件需要干涉模块转换的内容,所以需要使用它对应的 loader
        use: ExtractTextPlugin.extract({ 
          fallback: 'style-loader',
          use: 'css-loader',
        }), 
      },
    ],
  },
  plugins: [
    // 引入插件,配置文件名,这里同样可以使用 [hash]
    new ExtractTextPlugin('index.css'),
  ],
}

Css预处理器(Less/Sass)

Less/Sass可以说是在工作中必不可少的,webpack可以通过配置loader,来支持Less/Sass的运行

举个爪子,以Less为例子进行配置

需要用到less-loader

将 css-loader、 style-loader 和 less-loader 链式调用, 可以把所有样式立即应用于 DOM

module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [{
                    loader: 'style-loader'
                }, {
                    loader: 'css-loader'
                }, {
                    loader: 'less-loader' 
                }]
            }
        ]
    }
}
我们也可以修改上面的webpack配置

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.less$/,
        // 因为这个插件需要干涉模块转换的内容,所以需要使用它对应的 loader
        use: ExtractTextPlugin.extract({ 
          fallback: 'style-loader',
          use: [
            'css-loader', 
            'less-loader',
          ],
        }), 
      },
    ],
  },
  // ...
}

处理图片文件

在前端工作流程中,绝对少不了图片,虽然我们已经在css-loader样式解析中对url()引用文件路径做了处理,但webpack处理不了jpg/png/gif等文件格式,所以我们还需要添加一个loader来配置,让我们能够使用图片文件

file-loader可以用于处理很多类型的文件,它主要是通过输出文件,把构建后的文件路径返回。

配置file-loader,在rules中添加图片类型文件的解析配置

module.exports = {
    // ...
    modules: {
        // ...
        rules: [
            {
               test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {},
                    },
                ], 
            }
        ]
    }
}

PS: 还有一个loader可以处理,那就是url-loader

Babel 处理ES6、ES7标准

Babel是一个让我们能够使用ES新特性的JS编译工具,我们可以在webpack中配置Babel,让我们用ES6,ES7标准编写JS代码

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.jsx?/, // 支持 js 和 jsx
        include: [
          path.resolve(__dirname, 'src'), // src 目录下的才需要经过 babel-loader 处理
        ],
        loader: 'babel-loader',
      },
    ],
  },
}

我们需要在根目录下创建一个.babelrc文件来对Babel进行配置

启动静态服务

开启静态服务,可以通过webpack-dev-server在本地开启一个简单的静态服务来进行开发

安装webpack-dev-server,然后配置package.json

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

使用npm run start运行项目,访问http://localhost:8080/(默认访问的是index.html)

通过ctrl + C可以结束运行


webpack 如何解析代码模块路径

模块化开发的前端工作规范已经深入人心,在webpack支持的模块化中,我们可以使用import xxx from 'xxx.js

又例如我们用VUE进行开发的时候,必须使用import vue from 'Vue'webpack在构建的时候,会解析依赖,然后再去加载依赖的模块文件

那么webpack是如何将 xxx.js 或者 Vue 解析成模块文件路径呢❓

模块解析规则

  • 解析相对路径
    • 查找相对当前模块的路径下是否有对应文件或者是文件夹
    • 是文件则直接加载
    • 是文件夹则继续查找文件夹下的package.json
    • 存在package.json文件则按照文件中main字段的文件名来查找文件
    • 若无package.json或者没有main字段,则默认查找index.js
  • 解析模块名
    • 查找当前文件目录下,父级目录及以上目录下的node_modules文件夹,看是否有对应名称的模块
  • 解析绝对路径
    • 直接查找对应路径的文件

webpack中,和模块路径解析有关的配置都在resolve字段里

module.exports = {
    resolve: {
        //...
    }
}

resolve 常用配置

  • resolve.alias 设置别名

先假设我们有个utils模块用来作为我们的工具文件,并且在整个项目中经常需要引入,那么如果我们每次引入都用路径去引入,就会显得很麻烦,所以我们可以通过配置别名,达到import 'utils'的效果

alias: {
    utils: path.resolve(__dirname,'src/utils') // 通过path.resolve和__dirname来获取绝对路径
}

这样配置别名采用的是模糊匹配,即只要模块中携带了utils就会被替换掉

import 'utils/form.js' // 相当于引入了 'src/utils/form.js'

若想采用精确匹配,需要在别名后加上$

alias: {
    utils$: path.resolve(__dirname,'src/utils') // 通过path.resolve和__dirname来获取绝对路径
}

resolve.extensions

这个配置的作用与文件后缀名有关系。我们可以通过它定义在进行模块路径解析的时候,webpack自己去尝试补全后缀名来进行查找

例如上述utils目录下有一个common.js文件,你可以这样引用

import * as common from './src/utils/common'

webpack会尝试给你依赖的路径添加上extensions字段所配置的后缀,然后开始按照依赖路径查找所有可以命中src/utils/common.js文件

❗ 但如果是要引用src/style目录下的common.css文件,而你是import './src/style/common这样引入的,webpack则会报出无法解析模块的错误

解决方式: - 添加后缀 - 在extensions字段中添加.css的配置

resolve.modules

对于直接声明依赖名的模块(上述的Vue),webpack会类似Node.js一样进行路径搜索,搜索node_modules目录,这个目录就是使用resolve.modules字段进行配置的

resolve: {
    modules:['node_modules'], (默认就是node_modules)
}

技巧:通常我们不会去调整这个配置,但如果我们可以确定项目内所有第三方依赖模块都是在项目根目录下的node_modules中的话,那么可以在node_modules之前配置一个确定的绝对路径(这种配置可以轻微提高构建速度)

resolve: {
  modules: [
    path.resolve(__dirname, 'node_modules'), // 指定当前目录下的 node_modules 优先查找,如果有一些类库是放在其他地方的,你可以添加自定义的路径或者目录
    'node_modules',
  ],
},

resolve.mainFields (少用)

可用于配置入口文件,当引用的是一个模块或者是一个目录时,会先看是否存在package.json,存在是就会去查找某一字段下指定文件,这个某一个字段,就可以通过mainFields来配置

默认配置

resolve: {
  // 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
  mainFields: ['browser', 'module', 'main'],

  // target 的值为其他时,mainFields 默认值为:
  mainFields: ["module", "main"],
},

通常情况下,package.json不会声明brower或者module,所以就会去查找main字段里的文件了

resolve.mainFiles (少用)

当目录下没有package.json文件时,我们说会默认使用目录的index.js这个文件,其实这个通过mainFiles也是可以配置的(但通常我们无需修改这个配置)

默认配置

resolve: {
    mainFiles: ['index'] // 可以添加其他默认使用的文件名
}

resolve.resolveLoader (少用)

resolveLoader用于配置解析loader时的resolve配置,原本resolve的配置项在这个字段下基本都有

// 默认配置

resolve: {
  resolveLoader: {
    extensions: ['.js', '.json'],
    mainFields: ['loader', 'main'],
  },
}

这里提供的配置相对少用,我们一般遵从标准的使用方式,使用默认配置,然后把 loader 安装在项目根路径下的 node_modules 下就可以了。


配置loader

loader匹配规则

我们在使用loader的时候,都是在modules.rules中添加新的配置项,在该字段中,每一项被视为一条匹配使用loader的规则

最常用的例子

module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.jsx?/,    // 正则匹配条件
                include: [
                    path.resolve(__dirname,'src')
                ],  // 规定规则范围
                use: 'babel-loader' // 规则应用结果
            }
            // ...
        ]
    }
}

rules的匹配规则中有两个关键因素:一个是匹配条件,一个是匹配规则后的应用

匹配条件通常都是使用请求资源文件的绝对路径来进行匹配

官方文档里将匹配条件定义为resource,此外还有比较少用到的issuer,则是声明依赖请求的源文件的绝对路径

例如:在/xx/xxx/xxxx.js中声明引入import './src/utils.jsresource/xx/xxx/src/utils.jsissuer/xx/xxx/xxxx.js,规则条件会对这两个值进行尝试匹配

module.exports = {
    // ...
    rules: [
        {
            resource: {
                test: /\.jsx?/,
                include: [
                    path.resolve(__dirname,'src'),
                ],
            },
            // 如果是使用issuer来匹配,则是issuer: { test: ... }
            use: 'babel-loader'
        }
        // ...
    ]
}

规则条件匹配

webpack的规则提供了多种配置形式:

- test: ...     正则匹配特定条件
- include: ...       匹配特定路径
- exclude: ...       排除特定路径
- and: [...]        必须匹配数组中所有条件
- or: [...]     匹配数组中任意一个条件
- not: [...]        排除匹配数组中所有条件
上述这些条件的值可以是以下5种
- 字符串    必须以提供的字符串开始,所以是字符串的话,这里我们需要提供绝对路径
- 正则表达式        用正则表达式的方式判断条件
- 函数 (path) => string     返回true代表匹配成功
- 数组      至少包含一个条件的数组
- 对象      匹配所有属性值的条件    

module type

什么是module type module type即模块类型的概念,不同模块类型类似于配置了不同的loaderwebpack会有针对性地进行处理

5种模块类型

  • javascript/auto:即 webpack 3 默认的类型,支持现有的各种 JS 代码模块类型 —— CommonJS、AMD、ESM
  • javascript/esm:ECMAScript modules,其他模块系统,例如 CommonJS 或者 AMD 等不支持,是 .mjs 文件的默认类型
  • javascript/dynamic:CommonJS 和 AMD,排除 ESM
  • javascript/json:JSON 格式数据,require 或者 import 都可以引入,是 .json 文件的默认类型
  • webassembly/experimental:WebAssembly modules,当前还处于试验阶段,是 .wasm 文件的默认类型

如果不希望使用默认的类型的话,在确定好匹配规则条件时,我们可以使用 type 字段来指定模块类型,例如把所有的 JS 代码文件都设置为强制使用 ESM 类型:

{
    test: /\.js/,
    include: [
        path.resolve(__dirname,'src')
    ],
    type: 'javascript/esm', // 指定模块类型 ( 默认是 javascript/auto )
}

使用loader配置

rules: [
    {
        test: /\.less/,
        use: [
            'style-loader', // 直接用字符串表示loader
            {
                loader:'css-loader',
                options:{
                    importLoaders: 1
                }
            }, // 用对象表示 loader,可以传递loader配置等
            {
                loader: 'less-loader',
                options: {
                    noIeCompoat: true
                }, // 传递loader配置
            }
        ]
    }
]

上面这个例子种,use可以是一个数组,也可以是一个字符串或者表示loader的对象。

PS: 我们还可以使用options给对应的loader传递一些配置项

loader执行顺序

在一个匹配规则中可以配置使用多个loader,即一个模块文件可以经过多个loader的转换处理,执行的顺序是从最后配置的loader开始,由后配置到先配置的顺序,例如上个例子中,.less文件会经理less-loadercss-loader再到style-loader的处理,最后成一个可以打包的模块

从后配置到先配置的顺序是在同一个rule中的执行方案,那么又有另外一个问题!当一个模块文件同时匹配到多个ruleloader又会按照什么顺序去应用呢?

rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/,  // 排除node_modules
    loader: "eslint-loader",
  },
  {
    test: /\.js$/,
    exclude: /node_modules/, // 排除node_modules
    loader: "babel-loader",
  },
],

上面的例子中我们需要在babel-loader应用之前先应用eslint-loader,我们无法保证按照这种顺序执行

webpackrules中提供了一个enforce字段,用来配置当前ruleloader类型,默认是普通类型,而我们可以配置成pre或者是post

  • pre - 前置
  • post - 后置

所有的loader会按照前置 → 行内 → 普通 → 后置的顺序执行,所以我们要确保eslint-loaderbabel-loader之前使用,就需要添加enforce字段

rules:[
    {
        enforce: "pre", // 前置
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "eslint-loader"
    }
]

使用 noParse

module.noParse可以用于配置哪些模块文件的内容不需要进行解析。

对于一些不需要解析依赖的库(例如jquery),可以通过noParse来配置(可提高整体构建速度)

module.exports = {
    // ...
    module: {
        noParse: /jquery|lodash/, // 正则表达式

        <!-- 或者使用 function
        noParse(content) {
            return /jquery|lodash/.test(content)
        }, -->
    }
}

❗ 使用noParse进行忽略的模块文件中不能使用import / require / define等引用方式


配置plugin

pluginwebpack提供额外的功能,由于需要提供不同的功能,不同的插件本身的配置比较多样化

webpack插件可以上 github.com/webpack-con… 进行查阅

几个常用插件

DefinePlugin

DefinePluginwebpack的内置插件,不需要安装,直接通过webpack.DefinePlugin使用

作用:创建一些在编译时可以配置的全局常量,这些常量的值我们可以在webpack的配置中指定

module.exports = {
    // ...
    plugins: [
        new webpack.DefinePlugin({
            _GET:JSON.stringify('get'),   // const _GET = 'get'
            _POST:JSON.stringify('post'), // const _POST = 'post'
            _DELETE:JSON.stringify('delete'), // const _DELETE = 'delete'
            _PUT:JSON.stringify('put'), // const _PUT = 'put'
        })
    ]
}

这些配置好的全局常量,可以在代码中直接使用

console.log('this Api type is' + _GET)

配置规则

  • 如果配置的值是字符串,那么字符串会被当成代码片段来执行,其结果作为最终变量的值,例如'1 + 1',最后结果会是2
  • 如果配置的值不是字符串,也不是对象字面量,那么值会转换为一个字符串,例如true,会被转换成'true'
  • 如果配置的是一个对象字面量,那么该对象的所有key会以相同的方式定义

extract-text-webpack-plugin

作用:用来把依赖的css分离出来成为单独的文件

const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loaer',
                    use: 'css-loader'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin('index.css')  // 配置文件名
    ]
}
有时候我们构建的入口不止一个,那么ExtractTextPlugin会为每一个入口创建单独分离的文件

plugins: [
    new ExtractTextPlugin('[name].css')
]

❗ 使用ExtractTextPlugin,还需要调整loader对应的配置

ProvidePlugin

ProvidePluginwebpack内置的,直接通过webpack.ProvidePlugin来使用

作用: 引用某些模块作为程序运行时的变量,不必每次使用都需要import/require

plugins: [
    new webpack.ProvidePlugin({
        identifier:'xxx',  // 类似于 import 'xxx'  
        // identifier: ['xxx','xxxx']   类似于 import xxxx from 'xxxx'
    })
]

IgnorePlugin

IgnorePluginwebpack内置的,直接通过webpack.IgnorePlugin来使用

作用:用于忽略某些特定的模块,让webpack不把这些指定的模块打包进去,例如我们使用了moment.js,如果直接引用后,里面会有大量的i18n代码,会导致打包出来的文件比较大,而实际上我们并不需要,这就是IgnorePlugin的使用场景

plugins: [
    new webpack.IgnorePlugin(/^\.\/locale$/,/moment$/)
]

IgnorePlugin的参数

1 - 匹配引入模块路径的正则表达式
2 - 匹配模块的对应上下文,即所在目录名

配置webpack-dev-server

在开发流程中,我们在部署到生产环境之前,都应该是在本地上先开发,运行我们写的代码,我们称为本地环境,这个环境相当于提供了一个简单的服务器,用于访问webpack构建好的静态文件。

(在开发中我们用它来调式代码)

webpack-dev-serverwebpack提供的一个工具,可以基于当前的webpack配置快速启动一个静态服务.

modedevelopment时,会具备热更新的功能(实时根据修改刷新当前页面)

webpack-dev-server 官方文档

webpack.docschina.org/configurati…

webpack-dev-server的基础使用

webpack-dev-server是一个依赖包,需要手动安装,然后在已经有webpack配置文件的项目目录下直接使用即可

npm install webpack-dev-server -D

webpack-dev-server --mode development

我们也可以通过配置package.json来更改启动命令

package.json

{
    // ...
    "scripts": {
        "dev": "webpack-dev-server --mode development"
    }
}


然后命令行使用 npm run dev 即可运行

PS:webpack-dev-server默认使用8080端口,如果webpack已经配置好了html-webpack-plugin来构建html文件,那么当我们访问http://localhost:8080就可以看到index.html页面,而如果没有进行配置,那么webpack-dev-server会自己生成一个页面用于展示静态资源

配置 webpack-dev-server

devServer字段是webpack用于配置webpack-dev-server的核心,我们可以在其中实现修改端口等功能

常用 devServer 配置

- port      用于指定静态服务开启的端口(默认8080)
- host      用于指定静态服务的主机名(默认是localhost)
- publicPath        用于指定构建好的静态文件在浏览器中以什么路径去访问(默认是/)
    - 例如有一个构建好的文件 output.js,完整的访问路径为 http://localhost:8080/output.js , 如果配置了 publicPath: 'assets/',那么 output.js 的访问路径就是 http://localhost:8080/assets/output.js
    - 建议生产环境的 devServer.publicPath 与 output.publicPath 的值一致
- proxy     用于设置请求代理,即将特定URL的请求代理到另外一台服务器上(如果需要请求单独的后端服务API时,可以通过这个配置进行代理)

举个爪子
proxy: {
    '/api': {
        target: "http://localhost:8000", // 将URL中带有 /api 的请求代理到 http://localhost:8000 上
        pathRewrite: { '^/api', '' } // 去掉URL中的 api 部分
        changeOrigin: true // 本地会虚拟一个服务器接受你的请求并代你发送该请求,可以解决跨域问题
    }
}

- contentBase       用于配置提供额外静态文件内容的目录,即配置额外静态文件内容的访问路径(那些不经过webpack构建,但在webpack-dev-server中提供访问的静态资源)

举个爪子:
contenBase: path.join(__dirname, "public") // 当前目录下的 public
constBase: [ path.join(__dirname, "public"), path.join(__dirname, "assets" )}

- before & after        用于配置用于在`webpack-dev-server`定义额外的中间件
    - before        在`webpack-dev-server`静态资源中间件处理之后,可以用于拦截部分请求返回特定内容,或者实现简单的数据mock
    - after     在webpack-dev-server静态资源中间件处理之后,可以用于打印日志等操作

配置 webpack-dev-middleware 中间件

webpack-dev-middleware就是在Express中提供webpack-dev-server静态服务能力的一个中间件

webpack-dev-middleware是一个依赖,需要手动安装

npm install webpack-dev-middleware --save-dev


在装有express的node服务里
const middleware = require("webpack-dev-middleware")

app.use(middleware(xxx,{
    xxx
}))

实现一个简单的mock服务

在日常的工作中,前端人员常常会因为后端接口未完成或者数据返回参差不齐,导致页面开发完后,进度停滞不前,那么我们就需要mock服务来帮助我们模拟后端数据,而webpack-dev-serverbeforeproxy配置,又或者webpack-dev-middleware结合Express,都可以帮助我们实现简单的mock服务

当我们请求某一个特定的路径时(如/market/shopsList),可以访问我们想要的数据内容

我们先基于Express app实现一个简单的mock功能方法

module.export = function mock(app) {
    app.get('/market/shopsList', (req,res) => {
        res.json({
            data:'' // 模拟返回数据
        })
    })

    // ...
}

然后配置webpack-dev-server中的before字段

const mock= require('./mock')

before(app) {
    mock(app) // 调用mock函数
}

区分开发与生产环境

webpack 4.x引入了mode的概念,在运行webpacks时需要指定使用production还是development,这个功能说明我们需要具备运行两套构建环境的能力

当你指定了使用production mode时,默认会启动各种性能优化的功能,而development mode,则会开启debug工具,运行时打印详细错误信息。

拆分配置

我们可以将webpack的配置按照不同的环境拆分成多个文件,运行时直接根据环境变量加载对应的配置文件

举个爪子,当我们用webpack构建vue项目时,即vue init webpack projectname,我们可以看到build文件夹下已经区分了mode

根据这个例子,我们可以划分成下面几个文件

- webpack.base.js       基础部分,即共享的配置
- webpack.dev.js        开发环境使用的配置
- webpack.pro.js        生产环境使用的配置

如何处理这样的配置拆分

对于webpack的配置,其实就是对外暴露一个js对象,所以对于这个对象,我们可以使用js代码来修改

const config = {
    // webpack配置
}

config.plugins.push(...) // 修改这个配置对象

module.exports = config // 暴露这个对象

此外我们要有一个工具(webpack-merge)帮助我们合并多个配置对象,这样我们就可以很轻松地拆分webpack配置,然后判断当前的环境变量,使用工具将对应环境的配置对象整合后提供给webpack

举个爪子

基础共享部分(webpack.base.js

module.exports = {
    entry: ...,
    output: {
        ...
    },
    resolve: {
        ...
    },
    module: {
        ...
    },
    plugins: [
        ...
    ]
}
webpack.dev.js 
需求: 添加loader/plugin

const merge = require('webpack-merge')
const webpack = require('webpack')
const baseWebpackConfig = require('./webpack.base.js')

module.exports = merga(baseWebpackConfig, {
    module: {
        rules: [
            // 注意:当这里use的值是字符串或者是对象的话,会替换掉原本规则use的值
            ... 
        ]
    },
    plugins: [
        // plugins会与baseWebpackConfig中的plugins数组进行合并
        ...
    ]
})

小结

至此,webpack的大致功能已经有了一个系统的了解,那么如何熟悉熟练的使用webpack,则需要我们从实践中探索,依据不同的需求,配置适合项目的开发环境和本地环境,后续中,我会更新如何通过webpack优化项目

本文面向初学webpack的程序员,如果对你有帮助,点个👍哦