从零开始学webpack:进阶配置

·  阅读 632
从零开始学webpack:进阶配置

前一篇文章中介绍了一些webpack配置常用的loader和插件,并且完成了一个适合大多数场景的基础配置文件;本文将继续介绍webpack的配置,相对于上一篇文章,本文更加着重开发效率和个性化需求的配置

多页应用打包

前一篇文章中配置都只适用于单页应用的打包,但是在实际的工作也还会涉及到多页应用的项目开发;

webpack中多页应用与单页应用打包的不同之处主要体现在以下几点:

  • 多入口,不同的页面使用不同的入口文件
  • 多出口,入口和出口相对应,有多个入口也就有了多个出口
  • 多HTML,不同的入口可以输出不同的html文件

配置

<!-- webpack.config.js -->
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    entry: {
      main: './main.js',
      miniApp: './miniApp.js'  
    },
    output: {
        filename: [name].[hsah].js,
        path: path.resolve(__dirname, 'dist')
    },
    plugin: [
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'main.html',
            chunks: ['main']
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'miniApp.html',
            chunks: ['miniApp']
        })
    ]
    // 省略其他代码 
}
复制代码

[name]: 构建包的名称,它来源于entry中的key值,在单页应用中默认为main

chunks: 用于指定需要引入到html中的js文件;与它相反,还有一个excludeChunks参数,它用于指定不需要引入的js文件

静态数据拷贝

日常的开发中会遇到一些直接使用的文件,这些文件可能是js,也可能是css或者是图片,它们不需要经过打包,可以直接将他们拷贝到webpack构建目录;但是手动拷贝不仅麻烦并且容易出错(比如,修改之后忘记拷贝)

copy-webpack-plugin插件用于将指定文件/文件夹复制到构建目录;通过这个插件可以将静态文件直接复制到输出目录中

安装

npm install copy-webpack-plugin -D
复制代码

配置

<!-- webpack.config.js -->
const path = require('path')
const copyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
    plugin: [
        new copyWebpackPlugin([
            {
                from: path.resolve(__dirname, './public'),
                to: './dist'
            }
        ])
    ]
}
复制代码

from: 指定需要复制的文件夹

to: 指定复制后的文件夹

更多copyWebpackPlugin配置,可以参考官方文档

devTool(配置sourceMap)

在开发的过程中经常会通过控制台查看错误信息;但是打包之后的错误信息位置将会以构建后的js为基准,这样的错误信息因为没有定位到源码的错误位置,对调试很不友好

这个问题可以通过对devTool配置sourceMap来解决;sourceMap是一个源码映射文件,有多种格式可选,这里只列举几个有代表性的,更多sourceMap格式可以查看官方文档

  • source-map: 原始源代码,会单独生成源码文件,可以提示错误信息的列和行
  • eval-source-map: 原始源代码,不会产生单独的文件,但是可以显示行和列
  • cheap-module-source-map: 转换后的代码,生成单独的文件,可以提示行但不能提示列
  • cheap-module-eval-source-map: 原始源代码,集成在打包后的文件中,不会生成独立的文件,可以提示行,但不能提示列

综合实际使用情况和构建速度考虑,开发环境中一般使用cheap-module-eval-source-map

<!-- webpack.config.js -->
module.exports = {
    // 省略其他代码 
    devtool: 'cheap-module-eval-source-map'
}
复制代码

webpack解决跨域问题

跨域是前后端接口交互时一个很常见的问题,解决跨域发方式也有很多,这里主要介绍如何通过webpack的配置来解决跨域问题;

准备工作

先通过server.js在本地创建一个node服务, 启动本地的3000端口作为后端服务

<!-- server.js -->
const express = require('express')
const app = express()

app.get('/api/user', (req, res) => {
    res.json({name: '阿白Smile'})
})
app.listen(3000)
复制代码

使用命令行工具执行node server.js命令启动node服务,然后使用client.js向后端服务发送请求

<!-- client.js -->
const xhr = new XMLHtttpRequest()

xhr.open('GET', '/api/user', true)

xhr.onload = function() {
    console.log(xhr.response)
}
xhr.send()
复制代码

普通代理配置

接下来就通过webpack为前端服务配置一个代理,将前端的服务代理到后端的服务上,这样可以让前端和服务端在同一个服务下,以此来解决跨域问题

<!-- webpack.config.js -->
module.exports = {
    devServer: {
        prot: 3000,
        host: 'localhost'
        proxy: {
            '/api': 'http://localhost:3000'
        }
    }
}
复制代码

通过以上的配置,当client.js访问/api/user时,请求会被代理到http://localhost:3000/api/user

如果不想每次都在接口路径前加/api,或者后端的接口没有/api这一层路径,那么可以通过pathRewrite重写路径,将/api重写为空

<!-- webpack.config.js -->
module.exports = {
    devServer: {
        prot: 3000,
        host: 'localhost'
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                pathRewrite: {'/api': ''}
            }
        }
    }
}
复制代码

通过服务端启动webpack

通过服务端启动webpack,可以将前端和服务端启动在同一个服务上;当前后端都在同一个服务的时候自然就不存在跨域问题了

服务端启动webpack需要使用到webpack-dev-middleware中间件

安装

npm install webpack-dev-middleware -D
复制代码

配置

<!-- server.js -->
const express = require('express')
const webpack = require('webpack')
const middle = require('webpack-dev-middleware')
const app = express()

let config = require('./webpack.config.js')
let compiler = webpack(config)
app.use(middle(compiler))

app.get('/user', (req, res) => {
    res.json({name: '阿白Smile'})
})
app.listen(3000)
复制代码

使用这种方式的前提是能够操作服务器,不过既然都可以操作服务器了,那么还可以通过设置header来实现跨域

mock数据

webpack-dev-server提供了一个before钩子;它的第一个参数暴露了webpack-dev-server内部的express服务,通过这个服务,可以完成一些数据的mock

普通数据mock

<!-- webpack.config.js -->
module.exports = {
    devServer: {
        before(app) {
            app.get('/name', (req, res) => {
                res.json('阿白Smile')
            })
        }
    }
}   
复制代码

使用mockerAPI

mocker-api是一个为 REST API 创建mock的webpack-dev-server中间件。在后端服务还没有完成的时候,可以通过这个中间件进行mock数据

安装

npm install mocker-api -D
复制代码

配置

创建一个mock.js进行接口和数据mock

<!-- mock.js -->
module.exports = {
    'GET /userInfo/:id': (req, res) => {
        const { id } = req.params;
        // 省略查询
        return res.json({
          id,
          name: '阿白smile'
        });
    }
}
复制代码

配置到devServerbefore钩子

<!-- webpack.config.js -->
const path = require('path')
const apiMocker = require('mocker-api');
const mockApi = path.resolve('./mock.js')
module.exports = {
    devServer: {
        before(app) {
            mockApi(app, mockApi)
        }
    }
}  
复制代码

修改client.js文件,访问/userInfo接口

<!-- client.js -->
const xhr = new XMLHtttpRequest()

xhr.open('GET', '/userInfo/1', true)

xhr.onload = function() {
    console.log(xhr.response)
}
xhr.send()
复制代码

使用npm run dev命令执行脚本之后,在浏览器中打开,此时浏览器会打印出{"id":"1","name":"阿白smile"}

配置resolve属性

webpack启动后会从入口文件开始找出所有依赖的模块;resolve的作用就是告诉webpack如何寻找这些模块所对应的文件

alias (配置别名)

resolve.alias用于配置别名,通过别名可以把原来的导入路径映射到一个新的路径, 它可以让模块的引入更加简单;vue中常常为src文件夹设置一个别名为@

文件结构

|--src
|--|--assets
|--|--|--main.css
复制代码

配置

<!-- webpack.config.js -->
module.exports = {
    // 省略其他代码 
    resolve: {
        alias: {
            '@': './src'
        }
    }
}
复制代码

使用

import '@/assets/main.css'
复制代码

使用别名之后,别名会直接映射到别名所指定的路径;在使用相关模块时,可以使用更加简单的方式书写模块路径

modules

resolve.modules用于指定webpack解析模块时从哪些目录下搜索模块,默认情况下,webpack只从node_modules中搜索;

如果有一个模块是自己编写的文件,那么它就不需要到node_modules中取查找;比如, 我们经常会在src/components中编写一些组件,为了避免每次引用组件都写很长的路径,就可以通过modules配置来简化以下路径

文件结构

|--src
|--|--components
|--|--|--navMenu.vue
复制代码

配置

<!-- webpack.config.js -->
module.exports = {
    // 省略其他代码 
    resolve: {
        modules: ['./src/components', 'node_modules']
    }
}
复制代码

使用

import navMenu from 'navMenu'
复制代码

webpack在搜索模块时,会根据modules的配置从左到右开始搜索;所以,在使用navMenu时,会先从./src/components中查找,如果没有找到,则会去node_modules中查找;

extensions (扩展名配置)

在模块导入语句中没有带文件后缀时,webpack会自动带上尝试后缀去访问(默认带.js); resolve.extensions用于配置尝试访问的后缀列表,列表的优先级为从左到右

配置

<!-- webpack.config.js -->
module.exports = {
    // 省略其他代码 
    resolve: {
        extensions: ['.js', '.vue', '.json']
    }
}
复制代码

使用

import index from './index'
复制代码

上面的代码结合配置之后,会先尝试使用.js作为后缀去访问index,如果不存在则尝试使用.vue作为后缀去访问;以此类推,如果配置的后缀都尝试了还没有找到文件,则会报错,文件未找到

定义环境变量

在实际开发中,开发环境和线上生产环境往往会使用不同的服务和域名,并且不同的环境应该是可以自动切换的,这就需要在代码中使用环境变量来区分不同的环境,然后自动切换相应的服务和域名

webpack通过内置的DefinePlugin插件可以提供了一个区分环境的全局变量

DefinePlugin的键值都是一个标志符或者多个用 . 连接起来的标志符

  • 如果value是一个字符串,它会被当作一个代码片段来使用

  • 如果value不是字符串,它会被转化为字符串(包括函数)

  • 如果value是一个对象,它所有的 key 会被同样的方式定义(即全局可以直接访问对象的key,value则会应用DefinePlugin的键值规则)

  • 如果在一个 key 前面加了 typeof,它会被定义为 typeof 调用

    module.exports = { plugin: { new webpack.DefinePlugin({ DEV: JSON.stringify('dev') }) } }

    if (DEV) { // 开发 BASE_URL: '' // 开发服务 } else { // 生产 BASE_URL: '' // 生产服务 }

区分环境

通过环境变量可以进行环境的区分,但是代码中如果涉及到多处需要区分且每次都需要手动去配置,这不仅加大了工作量而且还容易出错;

一般的解决方案是将不同环境的配置分开到不同的文件,然后使用webpack-merge将其与基础配置进行合并

webpack.base.config.js: 基础配置文件 webpack.pro.config.js: 线上生产环境配置文件 webpack.dev.config.js: 开发环境配置文件

webpack-merge是一个函数,它提供了合并功能,接收一个或多个对象/数组,用于对象的合并与数组的连接,返回合并后的对象;在合并对象时,如果同一个key出现多次,则后面的覆盖前面的

安装

npm install webpack-merge
复制代码

配置

const merge = require('webpack-merge')
let result = merge({name: '阿白smile', age: 18}, {age: 24, location: '北京'})

// 合并后的结果
// {name: '阿白smile', age: 24, location: '北京'}
复制代码

webpack.base.config.js是基础的webpack配置,各个环境可以通用,所以webpack.base.config.js需要作为merge的第一个参数

开发环境的配置

<!-- webpack.dev.config.js -->
const baseWebpackConfig = require('./webpack.base.config')
const merge = require('webpack-merge')

let devWebpackConfig = merge(baseWebpackConfig, {
    mode: 'development',
    devServer: {
        // 省略其他代码
    }
})
moudle.export = devWebpackConfig
复制代码

生产环境的配置

<!-- webpack.pro.config.js -->
const baseWebpackConfig = require('./webpack.base.config')
const merge = require('webpack-merge')

let proWebpackConfig = merge(baseWebpackConfig, {
    mode: 'production',
    // 省略其他代码
})
moudle.export = proWebpackConfig
复制代码

配置命令脚本

<!-- package.json -->
"scripts": {
    "dev": "webpack-dev-server --config=webpack.dev.config.js"
    "build": "webpack --config=webpack.pro.config.js"
}
复制代码

热更新

热更新主要应用在开发环境,当代码更改之后页面上只更新被修改的部分,不需要刷新页面,对开发和调试非常有利;

webpack中的热更新的配置主要依赖devServer.hot以及webpack的内置插件HotModuleReplacementPlugin

<!-- webpack.config.js -->
module.exports = {
    devServer: {
        hot: true   // 启用热更新
    },
    plugin: [
        new webpack.HotModuleReplacementPlugin()    // 热更新插件
    ]
}
复制代码

以上的代码配置之后在浏览器查看,会发现还是会刷新整个页面;这个时候还需要在入口文件中做以下配置

if (module.hot) {
    module.hot.accept()
}
复制代码

module.hot用于通知webpack此模块可以用于热更新,更多信息可以参考官方文档

写在最后

通过本篇文章中介绍的webpack配置,可以满足更加个性化的开发需求以及更高效率的开发

本文所对应的配置源码已提交到我的github

END

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改